1 /**
2 Type that implements the TarGz Filter used with the Archive template.
3 
4 Copyright: Copyright Richard W Laughlin Jr. 2014—2016
5 
6 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
7 
8 Authors: Richard W Laughlin Jr.
9 
10 Source: http://github.com/rcythr/archive 
11 
12 Filter class which can be used by the Archive class to compress/decompress files into a .gz format.
13 
14 The only current available alias which uses this filter is the TarGzArchive.
15 
16 Reading Usage:
17 ---
18 import archive.targz;
19 import std.stdio;
20 
21 auto archive = new TarGzArchive(std.file.read("my.tar.gz");
22 
23 foreach(file; archive.files)
24 {
25     writeln("Filename: ", file.path);
26     writeln("Data: ", file.data);
27 }
28 
29 ---
30 
31 Writing Usage:
32 ---
33 import archive.targz;
34 
35 auto archive = new TarGzArchive();
36 
37 auto file = new TarGzArchive.File("languages/awesome.txt");
38 file.data = "D\n"; // can also set to immutable(ubyte)[]
39 archive.addFile(file);
40 
41 std.file.write("lang.tar.gz", cast(ubyte[])archive.serialize());
42 
43 ---
44 
45 */
46 
47 module archive.targz;
48 
49 import archive.core;
50 import archive.tar;
51 
52 private import etc.c.zlib;
53 private import std.algorithm;
54 private import std.array;
55 private import std.zlib;
56 
57 /**
58  * Filter class which can be used by the Archive class to compress/decompress
59  *   files into a .gz format.
60  */
61 public class GzFilter(int L)
62 {
63     /**
64      * Input data is wrapped with gzip and returned.
65      */
66     public static void[] compress(void[] data)
67     {
68         auto result = appender!(ubyte[])();
69         
70         Compress compressor = new Compress(L, HeaderFormat.gzip);
71         for(uint i=0; i < data.length; i += 1024)
72         {
73             result.put(cast(ubyte[])compressor.compress(data[i .. min(i+1024, data.length)]));
74         }
75         result.put(cast(ubyte[])compressor.flush());
76         
77         return result.data;
78     }
79     
80     /**
81      * Input data is processed to extract from the gzip format.
82      */
83     public static void[] decompress(void[] data)
84     {
85         auto result = appender!(ubyte[])();
86         
87         UnCompress uncompressor = new UnCompress();
88         for(uint i=0; i < data.length; i += 1024)
89         {
90             result.put(cast(ubyte[])uncompressor.uncompress(data[i .. min(i+1024, data.length)]));
91         }
92         result.put(cast(ubyte[])uncompressor.flush());
93         
94         return result.data;
95     }
96 }
97 
98 /**
99  * Convenience alias that simplifies the interface for users
100  */
101 alias TarGzArchive = Archive!(TarPolicy, GzFilter!6); 
102 
103 unittest
104 {
105     string data1 = "HELLO\nI AM A FILE WITH SOME DATA\n1234567890\nABCDEFGHIJKLMOP";
106     immutable(ubyte)[] data2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
107 
108     TarGzArchive output = new TarGzArchive();
109 
110     // Add file into the top level directory.
111     TarGzArchive.File file1 = new TarGzArchive.File();
112     file1.path = "apple.txt";
113     file1.data = data1;
114     output.addFile(file1);
115 
116     // Add a file into a non top level directory.
117     TarGzArchive.File file2 = new TarGzArchive.File("directory/directory/directory/apple.txt");
118     file2.data = data2;
119     output.addFile(file2);
120 
121     // Add a directory that already exists.
122     output.addDirectory("directory/");
123     
124     // Add a directory that does not exist.
125     output.addDirectory("newdirectory/");
126 
127     // Remove unused directories
128     output.removeEmptyDirectories();
129     
130     // Ensure the only unused directory was removed.
131     assert(output.getDirectory("newdirectory") is null);
132 
133     // Re-add a directory that does not exist so we can test its output later.
134     output.addDirectory("newdirectory/");
135 
136     // Serialize the zip archive and construct a new zip with it
137     TarGzArchive input = new TarGzArchive(output.serialize());
138 
139     // Make sure that there is a file named apple.txt and a file named directory/directory/directory/apple.txt
140     assert(input.getFile("apple.txt") !is null);
141     assert(input.getFile("directory/directory/directory/apple.txt") !is null);
142 
143     // Make sure there are no extra directories or files
144     assert(input.numFiles() == 2);
145     assert(input.numDirectories() == 4);
146     assert(input.numMembers() == 6);
147 }