Skip to content

Commit

Permalink
feat: 新增重复文件检测,新增硬链删除
Browse files Browse the repository at this point in the history
  • Loading branch information
likun7981 committed Dec 3, 2020
1 parent 418f4bf commit b1f2085
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 42 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_size = 2

[*.yml]
indent_style = space
Expand Down
25 changes: 16 additions & 9 deletions cli.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
#!/usr/bin/env node
"use strict";
const meow = require("meow");
const hardLink = require("./lib/index");
const hardLink = require("./lib/index");

const cli = meow(
`
Usage
$ hlink [source] [dist]
$ hlink [source] [dist]
Options
--saveLevel,-l [Default: 1]
说明:如果源视频文件目录结构是 /share/download/movie/a.mkv,硬链接目的地目录是/share/media
saveLevel=2 时 结果就是 "/share/media/download/movie/a.mkv"
saveLevel=1 时 结果就是 "/share/media/movie/a.mkv"
Options
--saveLevel,-l [Default: 0]
saveLevel=2 只保存文件
saveLevel=1 保存一级目录
saveLevel=0 保存原有的相对源地址的路径
--ext,-e [Default: mkv,mp4,rmvb]
--maxFindLevel [Default: 4]
--maxFindLevel,-m [Default: 4] 删除硬链
--delete,-d 删除目标地址所有硬链
delete=1 表示删除目录
delete=0 表示只删除文件
`,
{
flags: {
saveLevel: {
type: "string",
default: "1",
default: "0",
alias: "s"
},
ext: {
Expand All @@ -33,6 +36,10 @@ const cli = meow(
type: "string",
default: "4",
alias: "m"
},
delete: {
type: 'string',
alias: 'd'
}
}
}
Expand Down
89 changes: 56 additions & 33 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,20 @@ const warning = require("./warning");
const execa = require("execa");
const log = require("./log");
const chalk = require("chalk");
const {
checkLinkExist,
checkDirectory,
checkFindLevels,
checkLevels,
getLinkPath,
getDirBasePath
} = require('./utils');

const resolvePath = path.resolve;

function checkLevels(levels) {
warning(Number.isNaN(levels), "保存的最大层级saveDirLevel必须设置为数字");
warning(
levels > 2 || levels < 0,
"保存的最大层级saveDirLevel只能设置为0/1/2"
);
}
function checkFindLevels(levels) {
warning(Number.isNaN(levels), "查找的最大层级maxFindLevel必须设置为数字");
warning(levels > 6 || levels < 1, "保存的最大层级maxFindLevel不能小于1大于6");
}

function checkDirectory(source, dest) {
fs.ensureDirSync(dest);
warning(source === dest, "起始地址和目标地址不能相同");
}

function getRealDestPath(sourcePath, destPath, saveLevels, startPath, s) {
if (saveLevels > 0) {
if (saveLevels !== 2) {
const relativePath = path
.relative(startPath, path.resolve(sourcePath)) || s.replace(path.extname(s), '');
return path.resolve(
Expand All @@ -50,7 +42,7 @@ function hardLink(input, options) {
dest = resolvePath(input[1]);
}
checkDirectory(source, dest);
const { s, e, m } = options;
const { s, e, m, d } = options;
const exts = e.split(",");
const saveLevels = +s;
const maxFindLevels = +m;
Expand All @@ -59,13 +51,18 @@ function hardLink(input, options) {
const messageMap = {
e: " 包含的后缀有:",
m: " 源地址最大查找层级为:",
s: " 硬链保存源地址的目录层级数为:"
s: " 硬链保存模式:"
};
log.info("开始创建硬链...");
log.info("当前配置为:");
Object.keys(messageMap).forEach(k => {
log.info(`${messageMap[k]}${chalk.cyanBright(options[k])}`);
});
if (!d) {
log.info("开始创建硬链...");
log.info("当前配置为:");
Object.keys(messageMap).forEach(k => {
log.info(`${messageMap[k]}${chalk.cyanBright(options[k])}`);
});
} else {
log.info("开始删除硬链...")
log.info(`删除模式为: ${chalk.cyan(d === '0' ? '仅删除文件' : '删除对应目录')}`)
}
function start(sourcePath, currentLevel = 1) {
if (currentLevel > maxFindLevels) {
return;
Expand All @@ -78,32 +75,58 @@ function hardLink(input, options) {
// 地址继续循环
start(filePath, currentLevel + 1);
} else if (exts.indexOf(extname) > -1) {
// 做硬链接
const realDestPath = getRealDestPath(
sourcePath,
dest,
saveLevels,
source,
s,
);
const sourceNameForMessage = chalk.yellow(
path.relative(path.join(source, ".."), filePath)
);
const destNameForMessage = chalk.cyan(
path.relative(path.join(dest, ".."), path.join(realDestPath, s))
);
if (!!d) {
// 删除硬链接
try {
const linkPaths = getLinkPath(filePath, realDestPath, d === '1' && dest)
if (!linkPaths.length) {
log.warn(`没有找到 ${chalk.cyan(getDirBasePath(source, filePath))} 硬链接`);
}
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} 已删除`)
}
})
} catch (e) {
if (e.message === 'ALREADY_DELETE') {
log.warn(`目录 ${chalk.cyan(getDirBasePath(dest, realDestPath))} 已删除`)
}
}
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}" `
`源地址 ${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(
`目标地址 "${destNameForMessage}" 硬链已存在, 跳过源地址 "${sourceNameForMessage}" 硬链接创建`
`源地址"${sourceNameForMessage}"硬链已存在, 跳过创建`
);
}
}
Expand Down
73 changes: 73 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

const execa = require('execa');
const fs = require('fs-extra');
const warning = require('./warning');
const path = require('path');
const log = require('./log');
const chalk = require('chalk');

function getTopDir(file, destPath) {
while (path.dirname(file) !== destPath) {
file = path.dirname(file)
}
return file
}

/**
* dir /a/b
* filepath /a/b/c/d
* output: /b/c/d
*
*/

function getDirBasePath(dir, filepath) {
return path.join(path.basename(dir), path.relative(dir, filepath))
}


function getLinkPath(file, destPath, destDir) {
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') : [];
}
function checkLinkExist(file, destPath) {
const paths = getLinkPath(file, destPath)
return paths.length >= 1;
}



function checkLevels(levels) {
warning(Number.isNaN(levels), "保存的最大层级saveDirLevel必须设置为数字");
warning(
levels > 2 || levels < 0,
"保存的最大层级saveLevel只能设置为0/1/2"
);
}
function checkFindLevels(levels) {
warning(Number.isNaN(levels), "查找的最大层级maxFindLevel必须设置为数字");
warning(levels > 6 || levels < 1, "保存的最大层级maxFindLevel不能小于1大于6");
}

function checkDirectory(source, dest) {
fs.ensureDirSync(dest);
warning(source === dest, "起始地址和目标地址不能相同");
}

module.exports = {
checkLinkExist,
checkFindLevels,
checkDirectory,
checkLevels,
getLinkPath,
getDirBasePath
};

0 comments on commit b1f2085

Please sign in to comment.