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