typora 瘦身

typora 瘦身

1、参考资料

  • https://blog.csdn.net/zha_ojunchen/article/details/107029846

2、Why?

  • 当使用如下设置时,复制黏贴的图片会自动保存在这个 {filename}.assets 文件夹中
  • 问题来了,复制文件,图片会自动保存到 assets 文件夹,并在 Markdown 文件产生该图片链接语法![]()
  • 但是删除、修改 Markdown 中的图片链接语句,并不会删除 assets 文件夹中的图片,这导致了经常assets 文件夹会产生很大的无用图片,感觉这是是 Markdown 存储的一个痛点

image-20200712192154865

3、目标

  • 编写 Java 代码,对 Typora 进行瘦身,删除本地无用的图片

4、代码

  • 大致思路:使用递归遍历整个笔记根目录
    • 如果遇到 MD 文件,则使用正则表达式匹配文中所用到的图片标签:![](),进而获取文中所引用的图片名称
    • 我们将文中所引用的图片名称和本地图片名称做差集,便能得到无用的图片,删除即可
public class TyporaClean {

    public static void main(String[] args) {

        // 笔记存储根目录
        String noteRootPath;

        // 从命令行读取笔记存储根目录,否则使用默认值
        if (args == null || args.length == 0) {
            noteRootPath = "D:\\我的坚果云";
            // noteRootPath = "C:\\Users\\Heygo\\Desktop\\Debug";
        } else {
            noteRootPath = args[0];
        }

        // 执行 Typora 瘦身程序
        doClean(noteRootPath);
    }

    /**
     * 执行 Typora 瘦身程序
     * @param destPath  笔记存储根目录
     */
    private static void doClean(String destPath) {

        // 获取当前路径的File对象
        File destPathFile = new File(destPath);

        // 获取当前路径下所有的子文件和路径
        File[] allFiles = destPathFile.listFiles();

        // 遍历allFiles
        for (File curFile : allFiles) {

            // 获取curFile对象是否为文件夹
            Boolean isDirectory = curFile.isDirectory();

            // 如果是文件夹
            if (isDirectory) {

                // 获取当前curFile对象对应的绝对路径名
                String absolutePath = curFile.getAbsolutePath();

                // 如果是asset文件夹,则直接调过
                if (absolutePath.endsWith(".assets")) {
                    continue;
                }

                // 如果是文件夹,则继续执行递归
                doClean(absolutePath);

            } else {

                // 如果是文件,执行单个md文件的图片瘦身
                doSinglePicClean(curFile);

            }
        }
    }

    /**
     * 执行单个md文件的图片瘦身
     * @param curFile   MD文件的File对象
     */
    public static void doSinglePicClean(File curFile) {

        // 获取文件名
        String curFileName = curFile.getName();

        // 文件名后缀,我们只处理md文件
        Boolean isMd = curFileName.endsWith(".md");

        // 如果不是md文件,我们不处理
        if (!isMd) {
            return;
        }

        // 存储图片的文件夹名称
        String curFilNameWithoutMd = curFileName.replace(".md", "");
        String curAssetName = curFilNameWithoutMd + ".assets";

        // 创建asset文件夹所对应的file对象
        String curAssetAbsolutePath = curFile.getParent() + "\\" + curAssetName;
        File curAssetFile = new File(curAssetAbsolutePath);

        // 判断 assets文件夹是否存在,
        Boolean iscurAssetExist = curAssetFile.exists();
        // assets文件夹存在才执行清理操作
        if (iscurAssetExist) {

            //asset文件夹存在,则进行瘦身
            CleanUnnecessaryPic(curFile, curAssetFile);

        }
    }

    /**
     * 删除无用图片
     * @param curFile       MD文件的File对象
     * @param curAssetFile  MD文件对应的assets目录的File对象
     */
    public static void CleanUnnecessaryPic(File curFile, File curAssetFile) {

        // 获取所有图片的绝对路径
        File[] allPicFiles = curAssetFile.listFiles();

        // 获取md文件中用到的所有图片
        String[] usedPicNames = getUsedPicNames(curFile);

        //对比,并进行删除
        CleanUnusedPic(allPicFiles, usedPicNames);
    }

    /**
     * 取md文件中用到的所有图片
     * @param curFile   MD文件的File对象
     * @return
     */
    public static String[] getUsedPicNames(File curFile) {

        // 读取md文件内容
        String mdFileContent = readMdFileContent(curFile);

        // 图片名称
        // 图片路径存储格式:![image-20200603100128164](Typora 瘦身.assets/image-20200603100128164.png)
        /*
            \[.*\]:[image-20200603100128164]
                . :匹配任意字符
                * :出现0次或多次
            \(.+\):(IDEA快捷键.assets/image-20200603100128164.png)
                . :匹配任意字符
                + :出现1次或多次
         */
        String regex = "!\\[.*\\]\\(.+\\)";

        // 匹配文章中所有的图片标签
        Matcher matcher = Pattern.compile(regex).matcher(mdFileContent);

        // imageNames 用于存储匹配到的图片标签
        List<String> imageNames = new ArrayList<>();

        //遍历匹配项,将其添加至集合中
        while (matcher.find()) {

            // 得到当前图片标签
            String curImageLabel = matcher.group();

            // 放心大胆地使用"/"截取子串,因为文件名不能包含"/"字符
            Integer picNameStartIndex = curImageLabel.lastIndexOf("/") + 1;
            Integer picNameEndIndex = curImageLabel.length() - 1;
            // 得到图片名称
            String curImageName = curImageLabel.substring(picNameStartIndex, picNameEndIndex);

            // 添加至集合中
            imageNames.add(curImageName);

        }

        // 转换为数组返回
        String[] retStrs = new String[imageNames.size()];
        return imageNames.toArray(retStrs);
    }

    /**
     * 读取MD文件的内容
     * @param curFile   MD文件的File对象
     * @return
     */
    public static String readMdFileContent(File curFile) {

        // 存储md文件内容
        StringBuilder sb = new StringBuilder();

        // 当前行内容
        String curLine;

        // 装饰者模式:FileReader无法一行一行读取,所以使用BufferedReader装饰FileReader
        try (
                FileReader fr = new FileReader(curFile);
                BufferedReader br = new BufferedReader(fr);
        ) {
            // 当前行有内容
            while ((curLine = br.readLine()) != null) {
                sb.append(curLine + "\r\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 返回md文件内容
        return sb.toString();
    }

    /**
     * 清除无用的图片
     * @param allPicFiles   所有本地图片的File对象数组
     * @param usedPicNames  MD文件中使用到的图片名称数组
     */
    private static void CleanUnusedPic(File[] allPicFiles, String[] usedPicNames) {

        // assets文件夹中如果没有图片,则直接返回
        if (allPicFiles == null || allPicFiles.length == 0) {
            return;
        }

        // 获取asset文件夹的绝对路径
        String assetPath = allPicFiles[0].getParent();

        // 为了便于操作,将数组转换为List
        List<String> usedPicNameList = Arrays.asList(usedPicNames);

        // 遍历所有本地图片,看看有哪些图片没有被使用
        for (File curPicFile : allPicFiles) {

            // 如果没有被使用,则添加至unusedPicNames集合
            String curFileName = curPicFile.getName();
            boolean isUsed = usedPicNameList.contains(curFileName);
            if (!isUsed) {
                // 创建File对象,用于删除
                String curPicAbsolutePath = curPicFile.getAbsolutePath();

                // 测试用:打印输出
                System.out.println("已删除无用图片:" + curPicAbsolutePath);

                // 删除文件,看看回收站还有没有,并没有。。。
                curPicFile.delete();
            }

        }

    }

}

5、效果

  • 程序控制台输出的日志:

image-20200712193652927

6、警告

  • 谨慎使用,图片一旦删除无法找回,回收站都没得。。。
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值