first commit
This commit is contained in:
341
DCIT.Core/Services/FileScanService.cs
Normal file
341
DCIT.Core/Services/FileScanService.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using DCIT.Core.Models;
|
||||
using DCIT.Core.Utilities;
|
||||
|
||||
namespace DCIT.Core.Services
|
||||
{
|
||||
public class FileScanService
|
||||
{
|
||||
private static readonly TimeSpan RuleMatchTimeout = TimeSpan.FromSeconds(1);
|
||||
private readonly DiffSerializationService _diffSerializationService;
|
||||
|
||||
public FileScanService()
|
||||
: this(new DiffSerializationService(new BmpDiffCodec()))
|
||||
{
|
||||
}
|
||||
|
||||
public FileScanService(DiffSerializationService diffSerializationService)
|
||||
{
|
||||
if (diffSerializationService == null)
|
||||
{
|
||||
throw new ArgumentNullException("diffSerializationService");
|
||||
}
|
||||
|
||||
_diffSerializationService = diffSerializationService;
|
||||
}
|
||||
|
||||
public IList<FileScanItem> ScanReplaceCandidates(string sourceDirectory, string outputDirectory)
|
||||
{
|
||||
return ScanReplaceCandidates(sourceDirectory, outputDirectory, null);
|
||||
}
|
||||
|
||||
public IList<FileScanItem> ScanReplaceCandidates(string sourceDirectory, string outputDirectory, RuleFile ruleFile)
|
||||
{
|
||||
var items = new List<FileScanItem>();
|
||||
var hasOutputDirectory = !string.IsNullOrWhiteSpace(outputDirectory);
|
||||
var rules = GetEnabledRules(ruleFile, RuleTarget.FileName);
|
||||
var files = EnumerateWordFiles(sourceDirectory);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var relativePath = PathUtility.GetRelativePath(sourceDirectory, file);
|
||||
var relativeDirectory = Path.GetDirectoryName(relativePath) ?? string.Empty;
|
||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
|
||||
var extension = Path.GetExtension(file);
|
||||
var replacedFileNameWithoutExtension = ApplyRulesToFileName(fileNameWithoutExtension, rules);
|
||||
ValidateReplacementFileName(file, replacedFileNameWithoutExtension);
|
||||
|
||||
var datedFileName = string.Format("{0}-{1:yyyyMMdd}{2}", replacedFileNameWithoutExtension, DateTime.Today, extension);
|
||||
var outputPath = hasOutputDirectory
|
||||
? Path.Combine(outputDirectory, relativeDirectory, datedFileName)
|
||||
: string.Empty;
|
||||
var diffPath = hasOutputDirectory
|
||||
? Path.Combine(outputDirectory, relativeDirectory, string.Format("{0}-{1:yyyyMMdd}.diff", replacedFileNameWithoutExtension, DateTime.Today))
|
||||
: string.Empty;
|
||||
|
||||
items.Add(new FileScanItem
|
||||
{
|
||||
IsSelected = true,
|
||||
SourcePath = file,
|
||||
OutputPath = outputPath,
|
||||
DiffPath = diffPath,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public IList<FileScanItem> ScanRestoreCandidates(string replaceDirectory, string restoreDirectory, string diffInputFormat)
|
||||
{
|
||||
var hasRestoreDirectory = !string.IsNullOrWhiteSpace(restoreDirectory);
|
||||
var normalizedFormat = string.Equals(diffInputFormat, ".bmp", StringComparison.OrdinalIgnoreCase)
|
||||
? ".bmp"
|
||||
: ".diff";
|
||||
var selectedDiffs = Directory.Exists(replaceDirectory)
|
||||
? Directory.EnumerateFiles(replaceDirectory, "*" + normalizedFormat, SearchOption.AllDirectories).ToList()
|
||||
: new List<string>();
|
||||
var diffLookup = selectedDiffs.ToDictionary(
|
||||
path => BuildRestoreKey(replaceDirectory, path),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var wordFiles = EnumerateWordFiles(replaceDirectory);
|
||||
var items = new List<FileScanItem>();
|
||||
foreach (var wordFile in wordFiles)
|
||||
{
|
||||
var key = BuildRestoreKey(replaceDirectory, wordFile);
|
||||
if (!diffLookup.ContainsKey(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = PathUtility.GetRelativePath(replaceDirectory, wordFile);
|
||||
var relativeDirectory = Path.GetDirectoryName(relativePath) ?? string.Empty;
|
||||
var extension = Path.GetExtension(wordFile);
|
||||
var restoreFileName = ResolveRestoreFileName(diffLookup[key], wordFile);
|
||||
var restorePath = hasRestoreDirectory
|
||||
? Path.Combine(restoreDirectory, relativeDirectory, restoreFileName ?? Path.GetFileNameWithoutExtension(wordFile) + "-rcv" + extension)
|
||||
: string.Empty;
|
||||
|
||||
items.Add(new FileScanItem
|
||||
{
|
||||
IsSelected = true,
|
||||
SourcePath = wordFile,
|
||||
OutputPath = restorePath,
|
||||
DiffPath = diffLookup[key],
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public IList<string> ValidateReplaceDirectories(string sourceDirectory, string outputDirectory)
|
||||
{
|
||||
return PathUtility.ValidateDirectoryPair("原始文件夹", sourceDirectory, "替换文件夹", outputDirectory).ToList();
|
||||
}
|
||||
|
||||
public IList<string> ValidateRestoreDirectories(string replaceDirectory, string restoreDirectory)
|
||||
{
|
||||
return PathUtility.ValidateDirectoryPair("替换文件夹", replaceDirectory, "恢复文件夹", restoreDirectory).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateWordFiles(string rootDirectory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rootDirectory) || !Directory.Exists(rootDirectory))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
return Directory.EnumerateFiles(rootDirectory, "*.*", SearchOption.AllDirectories)
|
||||
.Where(path =>
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
if (fileName != null && fileName.StartsWith("~$", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
return extension.Equals(".doc", StringComparison.OrdinalIgnoreCase)
|
||||
|| extension.Equals(".docx", StringComparison.OrdinalIgnoreCase);
|
||||
})
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private string ResolveRestoreFileName(string diffPath, string wordFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var document = _diffSerializationService.ReadMetadata(diffPath);
|
||||
var sourceFileName = document == null || document.SourceDocument == null
|
||||
? null
|
||||
: document.SourceDocument.FileName;
|
||||
if (!string.IsNullOrWhiteSpace(sourceFileName))
|
||||
{
|
||||
var safeFileName = Path.GetFileName(sourceFileName);
|
||||
if (!string.IsNullOrWhiteSpace(safeFileName)
|
||||
&& string.Equals(safeFileName, sourceFileName, StringComparison.Ordinal)
|
||||
&& !ContainsInvalidFileNameCharacter(safeFileName))
|
||||
{
|
||||
return safeFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Legacy or placeholder diff files are still paired by basename and use the old preview naming.
|
||||
}
|
||||
|
||||
return Path.GetFileNameWithoutExtension(wordFile) + "-rcv" + Path.GetExtension(wordFile);
|
||||
}
|
||||
|
||||
private static IList<RuleDefinition> GetEnabledRules(RuleFile ruleFile, RuleTarget target)
|
||||
{
|
||||
if (ruleFile == null || ruleFile.Rules == null)
|
||||
{
|
||||
return new List<RuleDefinition>();
|
||||
}
|
||||
|
||||
return ruleFile.Rules
|
||||
.Where(rule => rule != null && rule.IsEnabled && !string.IsNullOrEmpty(rule.SearchText) && rule.Target == target)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static string ApplyRulesToFileName(string fileNameWithoutExtension, IList<RuleDefinition> rules)
|
||||
{
|
||||
var result = fileNameWithoutExtension ?? string.Empty;
|
||||
if (rules == null || rules.Count == 0 || string.IsNullOrEmpty(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
result = ApplyRuleToFileName(result, rule);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ApplyRuleToFileName(string text, RuleDefinition rule)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || rule == null || string.IsNullOrEmpty(rule.SearchText))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
if (rule.MatchMode == RuleMatchMode.RegularExpression)
|
||||
{
|
||||
var regex = new Regex(rule.SearchText, GetRegexOptions(rule), RuleMatchTimeout);
|
||||
return regex.Replace(text, match =>
|
||||
{
|
||||
if (!match.Success || match.Length <= 0)
|
||||
{
|
||||
return match.Value;
|
||||
}
|
||||
|
||||
return match.Result(rule.ReplacementText ?? string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
var escapedPattern = Regex.Escape(rule.SearchText);
|
||||
var plainRegex = new Regex(escapedPattern, GetRegexOptions(rule), RuleMatchTimeout);
|
||||
return plainRegex.Replace(text, match => rule.ReplacementText ?? string.Empty);
|
||||
}
|
||||
|
||||
private static string ApplyRuleToText(string text, RuleDefinition rule)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || rule == null || string.IsNullOrEmpty(rule.SearchText))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
if (rule.MatchMode == RuleMatchMode.RegularExpression)
|
||||
{
|
||||
var regex = new Regex(rule.SearchText, GetRegexOptions(rule), RuleMatchTimeout);
|
||||
return regex.Replace(text, match =>
|
||||
{
|
||||
if (!match.Success || match.Length <= 0)
|
||||
{
|
||||
return match.Value;
|
||||
}
|
||||
|
||||
if (rule.IsWholeWord && !IsWholeWord(text, match.Index, match.Length))
|
||||
{
|
||||
return match.Value;
|
||||
}
|
||||
|
||||
return match.Result(rule.ReplacementText ?? string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
var escapedPattern = Regex.Escape(rule.SearchText);
|
||||
var plainRegex = new Regex(escapedPattern, GetRegexOptions(rule), RuleMatchTimeout);
|
||||
return plainRegex.Replace(text, match =>
|
||||
{
|
||||
if (rule.IsWholeWord && !IsWholeWord(text, match.Index, match.Length))
|
||||
{
|
||||
return match.Value;
|
||||
}
|
||||
|
||||
return rule.ReplacementText ?? string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
private static RegexOptions GetRegexOptions(RuleDefinition rule)
|
||||
{
|
||||
var options = RegexOptions.CultureInvariant;
|
||||
if (!rule.IsCaseSensitive)
|
||||
{
|
||||
options |= RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private static bool IsWholeWord(string text, int start, int length)
|
||||
{
|
||||
var leftOk = start <= 0 || GetWordCharClass(text[start - 1]) != GetWordCharClass(text[start]);
|
||||
var rightIndex = start + length;
|
||||
var rightOk = rightIndex >= text.Length || GetWordCharClass(text[rightIndex - 1]) != GetWordCharClass(text[rightIndex]);
|
||||
return leftOk && rightOk;
|
||||
}
|
||||
|
||||
private static int GetWordCharClass(char character)
|
||||
{
|
||||
if ((character >= 'a' && character <= 'z')
|
||||
|| (character >= 'A' && character <= 'Z')
|
||||
|| (character >= '0' && character <= '9')
|
||||
|| character == '_')
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (character >= 0x4E00 && character <= 0x9FFF)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void ValidateReplacementFileName(string sourcePath, string fileNameWithoutExtension)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileNameWithoutExtension))
|
||||
{
|
||||
throw new InvalidDataException("文件名规则替换结果为空,无法生成输出文件名。源文件: " + sourcePath);
|
||||
}
|
||||
|
||||
if (ContainsInvalidFileNameCharacter(fileNameWithoutExtension))
|
||||
{
|
||||
throw new InvalidDataException("文件名规则替换结果包含非法文件名字符,无法生成输出文件名。源文件: "
|
||||
+ sourcePath
|
||||
+ ";替换后文件名: "
|
||||
+ fileNameWithoutExtension);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContainsInvalidFileNameCharacter(string fileName)
|
||||
{
|
||||
if (fileName == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0
|
||||
|| fileName.IndexOf(Path.DirectorySeparatorChar) >= 0
|
||||
|| fileName.IndexOf(Path.AltDirectorySeparatorChar) >= 0;
|
||||
}
|
||||
|
||||
private static string BuildRestoreKey(string rootDirectory, string path)
|
||||
{
|
||||
var relativePath = PathUtility.GetRelativePath(rootDirectory, path);
|
||||
var directory = Path.GetDirectoryName(relativePath) ?? string.Empty;
|
||||
var fileName = Path.GetFileNameWithoutExtension(relativePath);
|
||||
return Path.Combine(directory, fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user