Comparing version 0.2.1 to 0.2.2
30
cli.js
@@ -6,2 +6,6 @@ #!/usr/bin/env node | ||
const hardLink = require("./lib/index"); | ||
const exts = require('./lib/exts')([]); | ||
const inquirer = require('inquirer'); | ||
const path = require('path') | ||
const fs = require('fs-extra') | ||
@@ -14,11 +18,12 @@ const cli = meow( | ||
可配置选项: | ||
--saveLevel,-l [Default: 0] | ||
${chalk.gray(`saveLevel=2 只保存文件 | ||
saveLevel=1 保存一级目录 | ||
saveLevel=0 保存原有的相对源地址的路径`)} | ||
--ext,-e [Default: mkv,mp4,rmvb] | ||
--maxFindLevel,-m [Default: 4] 删除硬链 | ||
--delete,-d 删除目标地址所有硬链 | ||
${chalk.gray(`delete=1 表示删除目录 | ||
delete=0 表示只删除文件`)} | ||
--saveMode,-s 保存模式,默认为模式0 | ||
${chalk.gray(`saveMode=1 保存一级目录 | ||
saveMode=0 保存原有的相对源地址的路径`)} | ||
--ext,-e 额外需要做外链扩展名 | ||
${chalk.gray(`默认包含了常用了的视频扩展名: ${exts.join(',')}`)} | ||
--maxFindLevel,-m 查找文件的最大层级, 默认4层 | ||
--delete,-d 删除目标地址所有硬链,默认为false | ||
例子: | ||
@@ -29,5 +34,4 @@ ${chalk.grey(`# 创建 /share/download 下面文件到目标地址 /share/movie`)} | ||
${chalk.grey(`# 删除 /share/download 下面文件在 /share/movie 下面的对应硬链的文件夹`)} | ||
$ hlink -d=1 /share/download /share/movie | ||
$ hlink -d /share/download /share/movie | ||
说明: | ||
@@ -46,3 +50,3 @@ 1. 创建硬链时,会自动检测硬链接是否存在,硬链改名同样能检测到 | ||
type: "string", | ||
default: "mkv,mp4,rmvb", | ||
default: "", | ||
alias: "e" | ||
@@ -56,3 +60,3 @@ }, | ||
delete: { | ||
type: 'string', | ||
type: 'boolean', | ||
alias: 'd' | ||
@@ -59,0 +63,0 @@ } |
200
lib/index.js
@@ -15,2 +15,6 @@ const fs = require("fs-extra"); | ||
} = require('./utils'); | ||
const getExts = require('./exts'); | ||
const updateSourceAndDest = require("./updateSourceAndDest"); | ||
const configPath = require("./configPath"); | ||
const inquirer = require('inquirer'); | ||
@@ -21,28 +25,111 @@ const resolvePath = path.resolve; | ||
function getRealDestPath(sourcePath, destPath, saveLevels, startPath, s) { | ||
if (saveLevels !== 2) { | ||
const relativePath = path | ||
.relative(startPath, path.resolve(sourcePath)) || s.replace(path.extname(s), ''); | ||
return path.resolve( | ||
destPath, | ||
relativePath | ||
.split(path.sep) | ||
.slice(-saveLevels) | ||
.join(path.sep) | ||
); | ||
} | ||
const relativePath = path | ||
.relative(startPath, path.resolve(sourcePath)) || s.replace(path.extname(s), ''); | ||
return path.resolve( | ||
destPath, | ||
relativePath | ||
.split(path.sep) | ||
.slice(-saveLevels) | ||
.join(path.sep) | ||
); | ||
return destPath; | ||
} | ||
function hardLink(input, options) { | ||
warning(!input.length, "必须指定目标地址"); | ||
let source = resolvePath(process.cwd()); | ||
let dest = false; | ||
function getSource(answers) { | ||
const isSecondDir = answers.sourcePath === '二级目录'; | ||
const finalSourceDir = isSecondDir ? path.join(answers.sourcePathSecond, answers.secondDir) : answers.sourcePath; | ||
const sourceDir = isSecondDir ? path.dirname(finalSourceDir) : finalSourceDir; | ||
return [finalSourceDir, sourceDir] | ||
} | ||
function parseInput(input) { | ||
if (input.length === 1) { | ||
dest = resolvePath(input[0]); | ||
} else if (input.length === 2) { | ||
source = resolvePath(input[0]); | ||
dest = resolvePath(input[1]); | ||
return [resolvePath(process.cwd()), resolvePath(input[0])] | ||
} else if (input.length >= 2) { | ||
return [resolvePath(input[0]), resolvePath(input[1])] | ||
} | ||
} | ||
async function hardLink(input, options) { | ||
const { s, e, m, d } = options; | ||
let source = false; | ||
let dest = false; | ||
const isDelete = !!d; | ||
let deleteDir = false; | ||
let isSecondDir = false; | ||
let sourceDir = false; | ||
if (!isDelete) { | ||
warning(!input.length, "必须指定目标地址"); | ||
const parsedPaths = parseInput(input); | ||
source = parsedPaths[0] | ||
sourceDir = parsedPaths[0] | ||
dest = parsedPaths[1] | ||
} else { | ||
const mapJsonNotExist = !fs.existsSync(configPath.mapJson) | ||
const noSaveRecord = !mapJsonNotExist && !Object.keys(fs.readJSONSync(configPath.mapJson)).length; | ||
warning((mapJsonNotExist || noSaveRecord) && !input.length, "你没有创建记录,你必须手动指定目标地址及源地址来进行删除操作"); | ||
const answerDeleteMode = await inquirer.prompt([{ | ||
type: 'rawlist', | ||
message: '请选择删除模式?', | ||
name: 'deleteDir', | ||
choices: ['仅仅删除文件', '删除硬链文件以及其所在目录'], | ||
default: '删除硬链文件以及其所在目录' | ||
}]) | ||
deleteDir = answerDeleteMode.deleteDir === '删除硬链文件以及其所在目录'; | ||
if (input.length) { | ||
const parsedPaths = parseInput(input); | ||
source = parsedPaths[0] | ||
sourceDir = parsedPaths[0] | ||
dest = parsedPaths[1] | ||
} else { | ||
const pathsMap = fs.readJSONSync(configPath.mapJson); | ||
const answers = await inquirer.prompt([{ | ||
type: 'rawlist', | ||
name: 'sourcePath', | ||
message: '请选择需要删除硬链的源地址', | ||
choices: Object.keys(pathsMap).concat('二级目录') | ||
}, { | ||
message: '你需要选择哪个文件夹的二级目录', | ||
type: 'rawlist', | ||
name: 'sourcePathSecond', | ||
when: (answer) => { | ||
return answer.sourcePath === '二级目录' | ||
}, | ||
choices: Object.keys(pathsMap).map(s => ({ | ||
value: s, | ||
name: s + '的二级目录' | ||
})) | ||
}, { | ||
type: 'rawlist', | ||
name: 'secondDir', | ||
message: "请选择一个二级目录作为源地址", | ||
when: (answer) => { | ||
return !!answer.sourcePathSecond | ||
}, | ||
choices: (answer) => { | ||
const file = fs.readdirSync(answer.sourcePathSecond).filter((s) => { | ||
return fs.statSync(path.join(answer.sourcePathSecond, s)).isDirectory() | ||
}) | ||
return file | ||
} | ||
}, { | ||
type: 'rawlist', | ||
name: 'destDir', | ||
message: "该源地址对应两个目标地址,请选择一个", | ||
when: (answer) => { | ||
const choiceDest = pathsMap[getSource(answer)[1]]; | ||
return choiceDest && choiceDest.length > 1; | ||
}, | ||
choices: (answer) => { | ||
return pathsMap[getSource(answer)[1]] | ||
} | ||
}]) | ||
const [realSource, createdSourceDir] = getSource(answers); | ||
source = realSource; | ||
sourceDir = createdSourceDir | ||
dest = answers.destDir || pathsMap[createdSourceDir][0]; | ||
isSecondDir = answers.sourcePath === '二级目录' | ||
} | ||
} | ||
checkDirectory(source, dest); | ||
const { s, e, m, d } = options; | ||
const exts = e.split(","); | ||
@@ -58,3 +145,3 @@ const saveLevels = +s; | ||
}; | ||
if (!d) { | ||
if (!isDelete) { | ||
log.info("开始创建硬链..."); | ||
@@ -67,3 +154,2 @@ log.info("当前配置为:"); | ||
log.info("开始删除硬链...") | ||
log.info(`删除模式为: ${chalk.cyan(d === '0' ? '仅删除文件' : '删除对应目录')}`) | ||
} | ||
@@ -75,3 +161,3 @@ function start(sourcePath, currentLevel = 1) { | ||
const sourceDirContent = fs.readdirSync(sourcePath); | ||
sourceDirContent.forEach(s => { | ||
sourceDirContent.forEach(async s => { | ||
const extname = path.extname(s).replace(".", ""); | ||
@@ -81,4 +167,4 @@ const filePath = resolvePath(sourcePath, s); | ||
// 地址继续循环 | ||
start(filePath, currentLevel + 1); | ||
} else if (exts.indexOf(extname) > -1) { | ||
await start(filePath, currentLevel + 1); | ||
} else if (getExts(exts).indexOf(extname.toLowerCase()) > -1) { | ||
const realDestPath = getRealDestPath( | ||
@@ -91,18 +177,12 @@ sourcePath, | ||
); | ||
if (!!d) { | ||
if (isDelete) { | ||
// 删除硬链接 | ||
try { | ||
const linkPaths = getLinkPath(filePath, realDestPath, d === '1' && dest) | ||
if (!linkPaths.length) { | ||
log.warn(`没有找到 ${chalk.cyan(getDirBasePath(source, filePath))} 硬链接`); | ||
} | ||
const linkPaths = getLinkPath(filePath, dest, deleteDir) | ||
linkPaths.map((removePath) => { | ||
execa.sync('rm', ['-r', removePath]) | ||
const deletePathMessage = chalk.cyan(getDirBasePath(dest, removePath)); | ||
if (d === '0') { | ||
log.info(`删除硬链文件成功 ${deletePathMessage} `) | ||
} else { | ||
log.info(`目录 ${deletePathMessage} 已删除`) | ||
} | ||
log.info(`${deleteDir ? '目录' : '硬链'} ${deletePathMessage} 已删除`) | ||
}) | ||
// } | ||
} catch (e) { | ||
@@ -113,27 +193,28 @@ if (e.message === 'ALREADY_DELETE') { | ||
} | ||
return | ||
} | ||
// 做硬链接 | ||
const sourceNameForMessage = chalk.yellow(getDirBasePath(source, filePath)); | ||
const destNameForMessage = chalk.cyan(getDirBasePath(dest, path.join(realDestPath, s))); | ||
try { | ||
fs.ensureDirSync(realDestPath); | ||
if (checkLinkExist(filePath, realDestPath)) { | ||
throw { stderr: 'File exists' } | ||
} | ||
execa.sync("ln", [filePath, realDestPath]); | ||
log.success( | ||
`源地址 ${sourceNameForMessage} 硬链成功, 硬链地址为 ${destNameForMessage}` | ||
); | ||
} catch (e) { | ||
if (!e.stderr) { | ||
console.log(e); | ||
process.exit(0); | ||
} | ||
if (e.stderr.indexOf("File exists") === -1) { | ||
console.log(e); | ||
} else { | ||
log.warn( | ||
`源地址"${sourceNameForMessage}"硬链已存在, 跳过创建` | ||
} else { | ||
// 做硬链接 | ||
const sourceNameForMessage = chalk.yellow(getDirBasePath(source, filePath)); | ||
const destNameForMessage = chalk.cyan(getDirBasePath(dest, path.join(realDestPath, s))); | ||
try { | ||
if (checkLinkExist(filePath, dest)) { | ||
throw { stderr: 'File exists' } | ||
} else { | ||
fs.ensureDirSync(realDestPath); | ||
} | ||
execa.sync("ln", [filePath, realDestPath]); | ||
log.success( | ||
`源地址 ${sourceNameForMessage} 硬链成功, 硬链地址为 ${destNameForMessage}` | ||
); | ||
} catch (e) { | ||
if (!e.stderr) { | ||
console.log(e); | ||
process.exit(0); | ||
} | ||
if (e.stderr.indexOf("File exists") === -1) { | ||
console.log(e); | ||
} else { | ||
log.warn( | ||
`源地址"${sourceNameForMessage}"硬链已存在, 跳过创建` | ||
); | ||
} | ||
} | ||
@@ -145,4 +226,5 @@ } | ||
start(source); | ||
updateSourceAndDest(sourceDir, dest, isDelete && !isSecondDir) | ||
} | ||
module.exports = hardLink; |
@@ -28,15 +28,8 @@ | ||
function getLinkPath(file, destPath, destDir) { | ||
function getLinkPath(file, destPath, deleteDir) { | ||
const out = execa.sync('ls', ['-i', file]).stdout; | ||
const fileNumber = out.split(' ')[0] | ||
let findOut = false | ||
try { | ||
findOut = execa.sync('find', [destPath, '-inum', fileNumber]).stdout | ||
} catch (e) { | ||
throw new Error('ALREADY_DELETE'); | ||
} | ||
if (destDir && !!findOut) { | ||
return [getTopDir(destPath, destDir)]; | ||
} | ||
return !!findOut ? findOut.split('\n') : []; | ||
findOut = execa.sync('find', [destPath, '-inum', fileNumber]).stdout | ||
return !!findOut ? findOut.split('\n').map(p => deleteDir ? path.dirname(p) : p) : []; | ||
} | ||
@@ -54,3 +47,3 @@ function checkLinkExist(file, destPath) { | ||
levels > 2 || levels < 0, | ||
"保存的最大层级saveLevel只能设置为0/1/2" | ||
"保存的最大层级saveLevel只能设置为0/1" | ||
); | ||
@@ -57,0 +50,0 @@ } |
{ | ||
"name": "hlink", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"description": "hlink", | ||
@@ -33,5 +33,6 @@ "license": "MIT", | ||
"chalk": "^4.1.0", | ||
"execa": "^2.1.0", | ||
"fs-extra": "^9.0.1", | ||
"meow": "^5.0.0", | ||
"execa": "^2.1.0" | ||
"inquirer": "^7.3.3", | ||
"meow": "^5.0.0" | ||
}, | ||
@@ -38,0 +39,0 @@ "devDependencies": { |
# hlink | ||
![img](./img.png) | ||
## 所需环境: | ||
nodejs 10 以上;node环境安装请自行百度哈 | ||
## 安装 | ||
```bash | ||
$ npm install -g hlink | ||
# 查看帮助 | ||
$ hlink --help | ||
``` | ||
## 功能: | ||
1. 批量创建源地址下面所有视频文件 硬链 到目标地址 | ||
2. 重复硬链检测,就算硬链接已改名也能检查到 | ||
3. 批量删除源地址在目标地址所创建的硬链 | ||
## 使用 | ||
![使用](./media/ghelp.png) | ||
## 效果截图 | ||
![创建](./media/gcreate.png) | ||
![重复创建](./media/gexist.png) | ||
![删除目录](./media/gdeletedir.png) | ||
![删除文件](./media/gdeletefile.png) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15761
12
401
31
5
5
+ Addedinquirer@^7.3.3
+ Addedansi-escapes@4.3.2(transitive)
+ Addedansi-regex@5.0.1(transitive)
+ Addedchardet@0.7.0(transitive)
+ Addedcli-cursor@3.1.0(transitive)
+ Addedcli-width@3.0.0(transitive)
+ Addedemoji-regex@8.0.0(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedexternal-editor@3.1.0(transitive)
+ Addedfigures@3.2.0(transitive)
+ Addediconv-lite@0.4.24(transitive)
+ Addedinquirer@7.3.3(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedmute-stream@0.0.8(transitive)
+ Addedos-tmpdir@1.0.2(transitive)
+ Addedrestore-cursor@3.1.0(transitive)
+ Addedrun-async@2.4.1(transitive)
+ Addedrxjs@6.6.7(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedstring-width@4.2.3(transitive)
+ Addedstrip-ansi@6.0.1(transitive)
+ Addedthrough@2.3.8(transitive)
+ Addedtmp@0.0.33(transitive)
+ Addedtslib@1.14.1(transitive)
+ Addedtype-fest@0.21.3(transitive)