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