using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO;
using SharpCompress.Archives.Zip;
using SharpCompress.Writers;
using SharpCompress.Archives;
using SharpCompress.Common;
using CommandLine;
using SharpCompress.Common.Zip;
using SharpCompress.Factories;
namespace Compress
{
class Options
{
[Option('i', "input", Required = true, HelpText = "输入路径")]
public string Input { get; set; }
[Option('o', "output", Required = true, HelpText = "输出路径")]
public string Output { get; set; }
[Option('s', "size", Required = false, HelpText = "单个文件大小,单位MB,默认4096,最小10,最大8192")]
public long VolumeSizeMB { get; set; }
}
class Program
{
const int MAX_CMD_LENGTH = 8000; // 命令行最大长度限制
static long SINGLE_FILE_MAX = 8L * 1024 * 1024 * 1024; // 单文件8GB以上分卷
static string _7zPath = "7za.exe";
static IEnumerable<string> GetFilesDepthFirst(string rootDir)
{
// 先处理子目录(按创建时间排序)
var dirs = Directory.GetDirectories(rootDir)
.OrderBy(d => Directory.GetCreationTimeUtc(d))
.ThenBy(d => d);
foreach (var dir in dirs)
{
foreach (var file in GetFilesDepthFirst(dir))
{
yield return file;
}
}
// 最后处理当前目录文件(按修改时间排序)
foreach (var file in Directory.GetFiles(rootDir)
.OrderBy(f => File.GetLastWriteTimeUtc(f)))
{
yield return file;
}
}
static void CompressWith7z(string workPath, string archivePath, List<string> files, long volumeSizeMB)
{
string args;
string outputDir = Path.GetDirectoryName(archivePath);
string fileName = Path.GetFileNameWithoutExtension(archivePath);
files = files.Select(x => GetRelativePathManual(workPath, x).Substring(Path.GetFileName(workPath).Length + 1)).ToList();
// 分卷参数(当需要分卷时)
string volumeArg = volumeSizeMB > 0 ? $"-v{volumeSizeMB}m" : "";
// 处理长文件列表
if (GetCommandLength(files) > MAX_CMD_LENGTH)
{
string listFile = Path.GetTempFileName();
File.WriteAllLines(listFile, files);
args = $"a -mx=9 {volumeArg} \"{Path.Combine(outputDir, fileName)}\" @\"{listFile}\"";
}
else
{
args = $"a -mx=9 {volumeArg} \"{Path.Combine(outputDir, fileName)}\" {string.Join(" ", files.Select(f => $"\"{f}\""))}";
}
using (Process process = new Process())
{
process.StartInfo.FileName = _7zPath;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true; // 允许重定向标准输出
//process.StartInfo.CreateNoWindow = true;
process.StartInfo.WorkingDirectory = workPath;
process.Start();
Console.WriteLine($"开始压缩:{fileName}");
string output = process.StandardOutput.ReadToEnd(); // 读取输出内容
process.WaitForExit();
Console.WriteLine(output);
if (process.ExitCode == 0)
{
Console.WriteLine($"压缩完成:{fileName}");
}
else
{
Console.WriteLine($"压缩失败:{fileName} (错误码: {process.ExitCode})");
}
}
}
static int GetCommandLength(List<string> files)
{
// 估算命令行长度(每个文件路径长度+3个引号空格)
return files.Sum(f => f.Length + 3) + 50; // 50为其他参数长度余量
}
static void ProcessFiles(string inputDir, string outputDir, long volumeSizeMB)
{
var allFiles = GetFilesDepthFirst(inputDir).ToList();
var fileGroups = new List<List<string>>();
var currentGroup = new List<string>();
long currentSize = 0;
foreach (var file in allFiles)
{
var fileInfo = new FileInfo(file);
long fileSize = fileInfo.Length;
// 大文件单独处理
if (fileSize > 4L * 1024 * 1024 * 1024)
{
if (currentGroup.Count > 0)
{
fileGroups.Add(currentGroup);
currentGroup = new List<string>();
}
fileGroups.Add(new List<string> { file });
continue;
}
// 普通文件分组
if (currentSize + fileSize > volumeSizeMB * 1024L * 1024)
{
fileGroups.Add(currentGroup);
currentGroup = new List<string>();
currentSize = 0;
}
currentGroup.Add(file);
currentSize += fileSize;
}
if (currentGroup.Count > 0) fileGroups.Add(currentGroup);
// 并行处理压缩任务
Parallel.ForEach(fileGroups, (group, state, index) =>
{
string archiveName = $"{Path.GetFileName(inputDir)}_{index + 1:D4}";
bool isSingleBigFile = group.Count == 1 &&
new FileInfo(group[0]).Length > 4L * 1024 * 1024 * 1024;
// 确定分卷参数
long volSize = 0;
if (isSingleBigFile &&
new FileInfo(group[0]).Length > SINGLE_FILE_MAX)
{
volSize = volumeSizeMB;
}
CompressWith7z(
inputDir,
Path.Combine(outputDir, $"{archiveName}.7z"),
group,
volSize
);
});
}
static void Main(string[] args)
{
// 检查7z是否可用
try
{
using (Process process = new Process())
{
process.StartInfo.FileName = _7zPath;
process.StartInfo.Arguments = "";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
}
}
catch
{
Console.WriteLine("找不到7z.exe,请确保已安装并添加到环境变量");
return;
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(opts =>
{
string inputDir = Path.GetFullPath(opts.Input).TrimEnd(Path.DirectorySeparatorChar, '/');
string outputDir = Path.Combine(
Path.GetFullPath(opts.Output),
Path.GetFileName(inputDir));
Directory.CreateDirectory(outputDir);
long volumeSize = opts.VolumeSizeMB > 10?opts.VolumeSizeMB : 10;
ProcessFiles(inputDir, outputDir, volumeSize);
})
.WithNotParsed(errs => errs.ToList().ForEach(e => Console.WriteLine(e)));
}
public static string GetRelativePathManual(string basePath, string fullPath)
{
if (basePath == fullPath) return "";
var baseUri = new Uri(basePath);
var fullUri = new Uri(fullPath);
return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fullUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
}
}