导入文件夹内文件

导入文件夹内文件 公开

如何安装动作?

适用于
分类
文件处理 功能增强 功能


更多信息
分享时间 2022-08-27 12:44
最后更新 2022-10-09 17:02
修订版本 11
限制再分享
Quicker版本 1.35.38
动作大小 45.9 KB

分享到

「导入 Eagle, 导入指定多个文件夹内的所有文件」

简介

快速使用:

1. 运行动作会提示输入需要导入的文件夹路径

2. 关闭窗口后, 再次运行动作就会导入到 Eagle 了.


右键菜单选项:

1. 导入路径设置: 可以重新打开上面的路径设置窗口.

2. 移入回收站: 可以把设置的路径内所有文件都移入回收站. (按住 Ctrl 不松运行动作)

3. 点击关闭\开启自动移入回收站: 导入完毕后自动移入回收站.



动作制作记录:

2022-08-27 12:58:28: 因为暂时无法检测到 Eagle 是否导入完成, 怕误删文件, 所以自动删除就做不了, 就做了右键菜单中手动的移入回收站.
Eagle 有自动导入选项, 需要把文件移动到 Eagle 中设置的文件夹内, 然后 Eagle 自己再导入.
一个是比较消耗固态硬盘寿命, 一个是导入的时间会翻倍, 做成动作要用户设置自动导入的路径也比较麻烦, 不适合写到动作步骤中.


2022-09-28 21:47:56: 通过监视 Eagle Log 文件实现了自动删除功能.

2022-10-02 01:03:17: 导入 1000 张图测试, 在第 620 张图的时候触发了移入回收站, 原因是目前检测的 log 词条出现了. 目前只在这么大数量的导入上出现过这个问题.
如果您出现了文件被移入了回收站导致导入失败一部分, 请不要关闭 Eagle 的导入失败界面. 先去回收站把文件还原, 再点击全部重试就行. 之后在动作右键菜单中手动删除.
以后我再看看有没有其他办法能够稳定的实现这个功能, 导入大量文件也能用.

2022-10-02 16:13:00: 已修复导入大量文件时自动移入回收站提早触发的问题, 判断改为日志一秒未发生变化, 出现了指定的字符.

2022-10-09 17:06:10: 尝试修复导入 Eagle 在后台时不会触发自动删除的问题, 一共有3条日志出现时机极高概率是导入完成的时机. 为了避免出现了这些日志但还处于导入状态误删的问题. 加了判断, 后面是否还有 Add 日志的出现, 有就判断还未导入完成.

检测导入成功 C# 代码:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace EagleAPI
{
public static class Eagle
{
public static void Main()
{
Console.WriteLine(WatchEagleLogFile(DateTimeOffset.Now.ToUnixTimeMilliseconds()) ? "导入完成" : "超时");
}

/// <summary>
/// 通过监视 Eagle 的日志文件, 等待 Eagle 导入完成.
/// </summary>
/// <param name="startUnixTime">获取当前时间戳, 后面获取的 log 行只获取这个时间之后的.</param>
/// <param name="refreshInterval">间隔时间 毫秒</param>
/// <param name="timeAllow">检测生效间隔 毫秒 (即使检测到了, 日志必须有多长时间没变化才算数)</param>
/// <param name="timeout">超时时间 毫秒 (日志有多长时间没有变化)</param>
/// <param name="readLine">读取行数 (这个是最终监测行数)</param>
/// <returns>Eagle 导入完成, 返回 true; 超时, 返回 false</returns>
private static bool WatchEagleLogFile(long startUnixTime, int refreshInterval = 1000, int timeAllow = 1000, int timeout = 30000, int readLine = 100)
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Eagle\\Log.log";
// 读取行数 (这个是用来监测日志是否变化的行数, 为了节约性能这样设计的)
var readLineTemp = 2;
// 在日志文件中, 出现此字符串说明没在导入状态.
// (2022-10-2 14:46:42 导入600张的样子就失效了, 需要加间隔判断才能稳定)
const string infoIpcUpdateCollectModal = "[ipc] update-collect-modal";
const string infoBgNoChangesIgnoreUpdateCache = "[info] [bg] No Changes, ignore update cache.";
const string infoBgUpdateLibraryCacheSuccessfully = "[info] [bg] Update library cache successfully";
const string infoBgAdd = " [info] [bg] Add [";
string[] waitStrings =
{
infoIpcUpdateCollectModal,
infoBgNoChangesIgnoreUpdateCache,
infoBgUpdateLibraryCacheSuccessfully
};
while (true)
{
Thread.Sleep(refreshInterval);
// 读取日志最后指定行数的字符串, 去掉前后空白/
var backwardRead = BackwardRead(path, readLineTemp).Trim();
// 拆分成数组
var strArr = backwardRead.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
// 过滤日志
strArr = LogFilter(startUnixTime, strArr);
// 为空检测
if (strArr.Length < 1) return false;

// 获取日志未变化的时间
var noChangeInTime = ElapsedMilliseconds(strArr[0]);

// Console.WriteLine(strArr[0]);
// Console.WriteLine(noChangeInTime);
// Console.WriteLine(noChangeInTime > timeAllow);

// 开始检测时间, 如果超过了指定时间没有变化, 就读取指定行数来查找字符串是否出现.
if (noChangeInTime > timeAllow)
{
// 时间到了, 扫描多行字符串
readLineTemp = readLine;
// 判断几个导入完成的日志标记
if (waitStrings.Select(
x => FindContains(strArr, x)).Any(
index => index != -1 && FindContains(strArr.Take(index).ToArray(), infoBgAdd) == -1))
return true;
}

// 日志更新时间超时
if (noChangeInTime > timeout) return false;
}
}

/// <summary>
/// 过滤出指定以后的日志记录
/// </summary>
/// <param name="startUnixTime">开始时间戳</param>
/// <param name="strArr">需要过滤的数组</param>
/// <returns>返回过滤后的新数组</returns>
private static string[] LogFilter(long startUnixTime, IEnumerable<string> strArr)
{
var newArr = new List<string>();
foreach (var s in strArr)
{
if (LogTimeToUnixTimeMilliseconds(s) > startUnixTime)
{
newArr.Add(s);
}
else break;
}

return newArr.ToArray();
}

/// <summary>
/// 获取时间经过毫秒数 (当前时间与日志时间的差)
/// </summary>
/// <param name="str">单行日志的字符串</param>
/// <returns>返回时间差, 毫秒</returns>
private static long ElapsedMilliseconds(string str)
{
// 获取当前时间戳
var logUnixTime = LogTimeToUnixTimeMilliseconds(str);
// 获取时间经过毫秒数
var elapsedSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() - logUnixTime;
return elapsedSeconds;
}

/// <summary>
/// 日志时间转毫秒级时间戳
/// </summary>
/// <param name="str">需要转换的字符串</param>
/// <returns>返回时间戳</returns>
private static long LogTimeToUnixTimeMilliseconds(string str)
{
// 提取时间字符串
var time = str.Substring(1, 23);
// 设置字符串转时间格式
var format = new DateTimeFormatInfo {ShortDatePattern = "yyyy-MM-dd HH:mm:ss.fff"};
// 字符串转时间
var dateTime = Convert.ToDateTime(time, format);
// 取时间戳
var logUnixTime = new DateTimeOffset(dateTime).ToUnixTimeMilliseconds();
return logUnixTime;
}

/// <summary>
/// 查找指定子字符串出现的索引
/// </summary>
/// <param name="strArr">字符串数组</param>
/// <param name="value">查找的值</param>
/// <returns>返回查找到的索引</returns>
private static int FindContains(IReadOnlyList<string> strArr, string value)
{
for (var i = 0; i < strArr.Count; i++)
{
if (strArr[i].Contains(value)) return i;
}

return -1;
}

/// <summary>
/// 反向读取文本
/// </summary>
/// <param name="path">文本文件路径</param>
/// <param name="length">读取行数</param>
/// <returns>返回读取的字符串</returns>
private static string BackwardRead(string path, long length)
{
// 文件流:
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// 流位置从 0 开始的, length 1 开始, 多了 1 需要减掉.
var position = fs.Length - 1;
// 用于存储读出来的所有字符串
var sb = new StringBuilder(1024);
// 读取 10
for (var i = 0; i < length; i++)
{
// 反向读取一行
position = BackwardReadLine(fs, position, ref sb, true);
// 到底了就跳出循环
if (position <= 0) break;
}

// 文件流:
fs.Close();
// 返回读取的字符串
return sb.ToString();
}

/// <summary>
/// 反向读取一行文本 (碰到 \r\n 为一行)
/// </summary>
/// <param name="fs">文件流</param>
/// <param name="position">流中的位置, 也就是开始位置</param>
/// <param name="sb">需要插入到的总字符串</param>
/// <param name="reverse">false 插入到开头, 原文件顺序; true 插入到末尾, 原文件最末尾一行变成第一行</param>
/// <param name="maxRead">单行最大容量限制, 单位为字节</param>
/// <returns>返回当前流中的位置 </returns>
private static long BackwardReadLine(Stream fs, long position, ref StringBuilder sb, bool reverse = false, int maxRead = 10240)
{
// 空文件, 直接返回.
if (fs.Length == 0) return 0;
//回车符
const byte n = 13;
//换行符
const byte r = 10;
// 读取一个字节
var readByte = 0;
// 读取长度计数
var readLengthCount = 0;
// 存储读取的字节
var arr = new List<byte>();
// 存储读取完一行的文本
while (readByte != n && readByte != r)
{
// (位置到底 || 到达单行最大读取长度限制) 就跳出
if (position <= 0 || readLengthCount >= maxRead) break;
// 修改流中的位置为传进来的位置 (当前位置)
fs.Position = position;
// 读取一个字节
readByte = fs.ReadByte();
// 返回 -1 就是没读到, 没读到就跳出.
if (readByte == -1) break;
// 将读取的值插入到数组
arr.Insert(0, (byte) readByte);
// 流中的位置向前移动 1.
position--;
// 读取长度 +1
readLengthCount++;

// 问题: 调用两次方法, 进两次循环, 才能读到一行. 我打算读 10 行结果只读了 5 .
// 分析: Windows 换行是 \r\n 两个字符, 如果检测到其中一个就会跳出循环, 这里有两个连在一起, 就需要循环两次.
// 解决: 先多读一个字节, 看看是不是 \r \n, 如果是就一起读进去, 如果不是就说明读完一行了, 得跳出循环了.
if (readByte == r || readByte == n)
{
// 重新给一下上面自减后的位置
fs.Position = position;
// 读一个字节
readByte = fs.ReadByte();
// 没得读了就跳出循环.
if (readByte == -1) break;
// 如果是 \r \n , 就插入到这一行的数组中去, 位置自减
if (readByte == r || readByte == n)
{
// 进不来, 就不会修改下面这些变量的值了.
arr.Insert(0, (byte) readByte);
position--;
}
// 如果不是, 就说明这一行读完了, 因为前面 if 读到了 \r 或者 \n 才进来的, \r\n 分割行, 这里就直接跳出循环了.
else break;
}
// 如果不是 \r \n 就接着下一次循环获取, \r 或者 \n 就结束循环.
}

// List<byte> 转数组, UTF8 的字符串
var str = Encoding.UTF8.GetString(arr.ToArray());
// 插入到总字符串的首个位置
sb.Insert(reverse ? sb.Length : 0, str);
// 返回下一个需要读取的位置
return position;
}
}
}

最近更新

修订版本 更新时间 更新说明
11 2022-10-09 17:02 修复: 导入时 Eagle 在后台自动删除失效的问题.
10 2022-10-03 01:12 修改: 检测间隔修改为3秒
9 2022-10-02 19:39 优化: 路径输入中允许出现空行和前后空白.

最近讨论

暂无讨论