using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using API.Extensions; using API.Interfaces; using Microsoft.Extensions.Logging; namespace API.Services { /// /// Responsible for manipulating Archive files. Used by almost exclusively. /// public class ArchiveService : IArchiveService { private readonly ILogger _logger; public ArchiveService(ILogger logger) { _logger = logger; } public bool ArchiveNeedsFlattening(ZipArchive archive) { // Sometimes ZipArchive will list the directory and others it will just keep it in the FullName return archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName) || archive.Entries.Any(e => e.FullName.Contains(Path.AltDirectorySeparatorChar)); // return archive.Entries.Count > 0 && // archive.Entries.Any(e => e.FullName.Contains(Path.AltDirectorySeparatorChar)); //return archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName); } /// /// Extracts an archive to a temp cache directory. Returns path to new directory. If temp cache directory already exists, /// will return that without performing an extraction. Returns empty string if there are any invalidations which would /// prevent operations to perform correctly (missing archivePath file, empty archive, etc). /// /// A valid file to an archive file. /// Path to extract to /// public void ExtractArchive(string archivePath, string extractPath) { if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath)) { _logger.LogError($"Archive {archivePath} could not be found."); return; } if (Directory.Exists(extractPath)) { _logger.LogDebug($"Archive {archivePath} has already been extracted. Returning existing folder."); return; } Stopwatch sw = Stopwatch.StartNew(); using ZipArchive archive = ZipFile.OpenRead(archivePath); var needsFlattening = ArchiveNeedsFlattening(archive); if (!archive.HasFiles() && !needsFlattening) return; archive.ExtractToDirectory(extractPath); _logger.LogDebug($"Extracted archive to {extractPath} in {sw.ElapsedMilliseconds} milliseconds."); if (needsFlattening) { sw = Stopwatch.StartNew(); _logger.LogInformation("Extracted archive is nested in root folder, flattening..."); new DirectoryInfo(extractPath).Flatten(); _logger.LogInformation($"Flattened in {sw.ElapsedMilliseconds} milliseconds"); } } } }