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