1 /** 2 * ae.sys.archive 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.sys.archive; 15 16 import std.array; 17 import std.datetime; 18 import std.exception; 19 import std.file; 20 import std.path; 21 import std.process; 22 import std.string; 23 24 import ae.sys.file; 25 import ae.sys.install.sevenzip; 26 import ae.utils.path : haveExecutable; 27 28 /// Unzips a .zip file to the target directory. 29 void unzip(string zip, string target) 30 { 31 import std.zip; 32 auto archive = new ZipArchive(zip.read); 33 foreach (name, entry; archive.directory) 34 { 35 auto path = buildPath(target, name).replace("\\", "/"); 36 ensurePathExists(path); 37 38 auto attr = entry.fileAttributes; 39 40 if (name.endsWith(`/`)) 41 { 42 if (!path.exists) 43 path.mkdirRecurse(); 44 } 45 else 46 { 47 bool isLink = false; 48 version (Posix) 49 { 50 import core.sys.posix.sys.stat; 51 if (S_ISLNK(attr)) 52 isLink = true; 53 } 54 if (isLink) 55 { 56 symlink(cast(string)archive.expand(entry), path); 57 continue; // Don't try to chmod the link target! 58 } 59 else 60 std.file.write(path, archive.expand(entry)); 61 } 62 63 if (attr) 64 path.setAttributes(attr); 65 66 auto time = entry.time().DosFileTimeToSysTime(UTC()); 67 path.setTimes(time, time); 68 } 69 } 70 71 /// Unpacks a file with 7-Zip to the specified directory, 72 /// installing it locally if necessary. 73 void un7z(string archive, string target) 74 { 75 sevenZip.require(); 76 target.mkdirRecurse(); 77 auto pid = spawnProcess([sevenZip.exe, "x", "-o" ~ target, archive]); 78 enforce(pid.wait() == 0, "Extraction failed"); 79 } 80 81 /// Unpacks an archive to the specified directory. 82 /// Uses std.zip for .zip files, and invokes tar (if available) 83 /// or 7-Zip (installing it locally if necessary) for other file types. 84 /// Always unpacks compressed tar archives in one go. 85 void unpack(string archive, string target) 86 { 87 bool untar(string longExtension, string shortExtension, string tarSwitch, string unpacker) 88 { 89 if ((archive.toLower().endsWith(longExtension) || archive.toLower().endsWith(shortExtension)) && haveExecutable(unpacker)) 90 { 91 target.mkdirRecurse(); 92 auto pid = spawnProcess(["tar", "xf", archive, tarSwitch, "--directory", target]); 93 enforce(pid.wait() == 0, "Extraction failed"); 94 return true; 95 } 96 return false; 97 } 98 99 if (archive.toLower().endsWith(".zip")) 100 archive.unzip(target); 101 else 102 if (haveExecutable("tar") && ( 103 untar(".tar.gz", ".tgz", "--gzip", "gzip") || 104 untar(".tar.bz2", ".tbz", "--bzip2", "bzip2") || 105 untar(".tar.lzma", ".tlz", "--lzma", "lzma") || 106 untar(".tar.xz", ".txz", "--xz", "xz"))) 107 {} 108 else 109 if (archive.extension.toLower == ".rar" && haveExecutable("unrar")) 110 { 111 target.mkdirRecurse(); 112 auto pid = spawnProcess(["unrar", "x", archive, target]); 113 enforce(pid.wait() == 0, "Extraction failed"); 114 } 115 else 116 { 117 auto tar = archive.stripExtension; 118 if (tar.extension.toLower == ".tar") 119 { 120 un7z(archive, archive.dirName); 121 enforce(tar.exists, "Expected to unpack " ~ archive ~ " to " ~ tar); 122 scope(exit) tar.remove(); 123 un7z(tar, target); 124 } 125 else 126 un7z(archive, target); 127 } 128 }