3 namespace Robo\Task\Archive;
6 use Robo\Task\BaseTask;
7 use Robo\Task\Filesystem\FilesystemStack;
8 use Robo\Task\Filesystem\DeleteDir;
9 use Robo\Contract\BuilderAwareInterface;
10 use Robo\Common\BuilderAwareTrait;
13 * Extracts an archive.
15 * Note that often, distributions are packaged in tar or zip archives
16 * where the topmost folder may contain variable information, such as
17 * the release date, or the version of the package. This information
18 * is very useful when unpacking by hand, but arbitrarily-named directories
19 * are much less useful to scripts. Therefore, by default, Extract will
20 * remove the top-level directory, and instead store all extracted files
21 * into the directory specified by $archivePath.
23 * To keep the top-level directory when extracting, use
24 * `preserveTopDirectory(true)`.
28 * $this->taskExtract($archivePath)
30 * ->preserveTopDirectory(false) // the default
35 class Extract extends BaseTask implements BuilderAwareInterface
37 use BuilderAwareTrait;
52 private $preserveTopDirectory = false;
55 * @param string $filename
57 public function __construct($filename)
59 $this->filename = $filename;
63 * Location to store extracted files.
69 public function to($to)
76 * @param bool $preserve
80 public function preserveTopDirectory($preserve = true)
82 $this->preserveTopDirectory = $preserve;
91 if (!file_exists($this->filename)) {
92 $this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]);
96 if (!($mimetype = static::archiveType($this->filename))) {
97 $this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]);
102 // We will first extract to $extractLocation and then move to $this->to
103 $extractLocation = static::getTmpDir();
104 @mkdir($extractLocation);
105 @mkdir(dirname($this->to));
109 $this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]);
111 $result = $this->extractAppropriateType($mimetype, $extractLocation);
112 if ($result->wasSuccessful()) {
113 $this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]);
114 // Now, we want to move the extracted files to $this->to. There
115 // are two possibilities that we must consider:
117 // (1) Archived files were encapsulated in a folder with an arbitrary name
118 // (2) There was no encapsulating folder, and all the files in the archive
119 // were extracted into $extractLocation
121 // In the case of (1), we want to move and rename the encapsulating folder
124 // In the case of (2), we will just move and rename $extractLocation.
125 $filesInExtractLocation = glob("$extractLocation/*");
126 $hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0]));
127 if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) {
128 $result = (new FilesystemStack())
130 ->rename($filesInExtractLocation[0], $this->to)
132 (new DeleteDir($extractLocation))
136 $result = (new FilesystemStack())
138 ->rename($extractLocation, $this->to)
143 $result['time'] = $this->getExecutionTime();
149 * @param string $mimetype
150 * @param string $extractLocation
152 * @return \Robo\Result
154 protected function extractAppropriateType($mimetype, $extractLocation)
156 // Perform the extraction of a zip file.
157 if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
158 return $this->extractZip($extractLocation);
160 return $this->extractTar($extractLocation);
164 * @param string $extractLocation
166 * @return \Robo\Result
168 protected function extractZip($extractLocation)
170 if (!extension_loaded('zlib')) {
171 return Result::errorMissingExtension($this, 'zlib', 'zip extracting');
174 $zip = new \ZipArchive();
175 if (($status = $zip->open($this->filename)) !== true) {
176 return Result::error($this, "Could not open zip archive {$this->filename}");
178 if (!$zip->extractTo($extractLocation)) {
179 return Result::error($this, "Could not extract zip archive {$this->filename}");
183 return Result::success($this);
187 * @param string $extractLocation
189 * @return \Robo\Result
191 protected function extractTar($extractLocation)
193 if (!class_exists('Archive_Tar')) {
194 return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
196 $tar_object = new \Archive_Tar($this->filename);
197 if (!$tar_object->extract($extractLocation)) {
198 return Result::error($this, "Could not extract tar archive {$this->filename}");
201 return Result::success($this);
205 * @param string $filename
207 * @return bool|string
209 protected static function archiveType($filename)
211 $content_type = false;
212 if (class_exists('finfo')) {
213 $finfo = new \finfo(FILEINFO_MIME_TYPE);
214 $content_type = $finfo->file($filename);
215 // If finfo cannot determine the content type, then we will try other methods
216 if ($content_type == 'application/octet-stream') {
217 $content_type = false;
220 // Examing the file's magic header bytes.
221 if (!$content_type) {
222 if ($file = fopen($filename, 'rb')) {
223 $first = fread($file, 2);
225 if ($first !== false) {
226 // Interpret the two bytes as a little endian 16-bit unsigned int.
227 $data = unpack('v', $first);
230 // First two bytes of gzip files are 0x1f, 0x8b (little-endian).
231 // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
232 $content_type = 'application/x-gzip';
236 // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
237 // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
238 $content_type = 'application/zip';
242 // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
243 // See http://en.wikipedia.org/wiki/Bzip2#File_format
244 $content_type = 'application/x-bzip2';
250 // 3. Lastly if above methods didn't work, try to guess the mime type from
251 // the file extension. This is useful if the file has no identificable magic
252 // header bytes (for example tarballs).
253 if (!$content_type) {
254 // Remove querystring from the filename, if present.
255 $filename = basename(current(explode('?', $filename, 2)));
256 $extension_mimetype = array(
257 '.tar.gz' => 'application/x-gzip',
258 '.tgz' => 'application/x-gzip',
259 '.tar' => 'application/x-tar',
261 foreach ($extension_mimetype as $extension => $ct) {
262 if (substr($filename, -strlen($extension)) === $extension) {
269 return $content_type;
275 protected static function getTmpDir()
277 return getcwd().'/tmp'.rand().time();