2 // Tizen Web Device API
3 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
5 // Licensed under the Apache License, Version 2.0 (the License);
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
18 #include "UnZipExtractRequest.h"
32 #include <PlatformException.h>
34 #include "ArchiveFile.h"
35 #include "ArchiveUtils.h"
40 using namespace DeviceAPI::Common;
44 FilePathStatus getPathStatus(const std::string& path)
50 std::string npath = removeTrailingDirectorySlashFromPath(path);
53 if (stat(npath.c_str(), &sb) == -1) {
56 if(sb.st_mode & S_IFDIR) {
63 void divideToPathAndName(const std::string& filepath, std::string& out_path,
64 std::string& out_name)
67 size_t pos_last_dir = filepath.find_last_of("/\\");
68 if(pos_last_dir == std::string::npos) {
72 out_path = filepath.substr(0, pos_last_dir+1);
73 out_name = filepath.substr(pos_last_dir+1);
77 void createMissingDirectories(const std::string& path, bool check_first = true)
80 const FilePathStatus path_status = getPathStatus(path);
81 //LOGD("[%s] status: %d", path.c_str(), path_status);
82 if(FPS_DIRECTORY == path_status) {
87 const size_t extract_path_len = path.length();
88 for(size_t i = 0; i < extract_path_len; ++i) {
89 const char& cur = path[i];
90 if( (('\\' == cur || '/' == cur) && i > 0) || //handle left side from current /
91 (extract_path_len-1 == i) ) { //handle last subdirectory path
93 const std::string left_part = path.substr(0,i+1);
94 const FilePathStatus status = getPathStatus(left_part);
95 //LOGD("left_part: [%s] status:%d", left_part.c_str(), status);
97 if(FPS_DIRECTORY != status) {
98 //TODO investigate 0775 (mode) - Filesystem assumed that file should have parent mode
99 if(mkdir(left_part.c_str(), 0775) == -1) {
100 LOGE("Couldn't create new directory: %s errno:%s",
101 left_part.c_str(), strerror(errno));
102 //TODO check why mkdir return -1 but directory is successfully created
103 // throw UnknownException(
104 // "Could not create new directory");
111 void changeFileAccessAndModifyDate(const std::string& filepath, tm_unz tmu_date)
115 newdate.tm_sec = tmu_date.tm_sec;
116 newdate.tm_min = tmu_date.tm_min;
117 newdate.tm_hour = tmu_date.tm_hour;
118 newdate.tm_mday = tmu_date.tm_mday;
119 newdate.tm_mon = tmu_date.tm_mon;
121 if (tmu_date.tm_year > 1900) {
122 newdate.tm_year = tmu_date.tm_year - 1900;
124 newdate.tm_year = tmu_date.tm_year ;
126 newdate.tm_isdst = -1;
128 ut.actime = ut.modtime = mktime(&newdate);
129 if(utime(filepath.c_str(), &ut) == -1) {
130 LOGE("Couldn't set time for: [%s] errno:%s", filepath.c_str(), strerror(errno));
134 void UnZipExtractRequest::execute(UnZip& owner, const std::string& extract_path,
135 const std::string& base_strip_path,
136 BaseProgressCallback* callback)
138 UnZipExtractRequest req(owner, extract_path, base_strip_path, callback);
142 UnZipExtractRequest::UnZipExtractRequest(UnZip& owner,
143 const std::string& extract_path,
144 const std::string& base_strip_path,
145 BaseProgressCallback* callback) :
148 m_extract_path(extract_path),
149 m_base_strip_path(base_strip_path),
150 m_callback(callback),
154 m_delete_output_file(false),
155 m_close_unz_current_file(false),
156 m_new_dir_status(FPS_NOT_EXIST),
158 m_is_directory_entry(false)
161 LOGE("Callback is null");
162 throw UnknownException("Problem with callback functionality");
166 void UnZipExtractRequest::run()
170 getCurrentFileInfo();
172 if(m_is_directory_entry) {
173 handleDirectoryEntry();
179 UnZipExtractRequest::~UnZipExtractRequest()
182 fclose(m_output_file);
183 m_output_file = NULL;
186 if(m_delete_output_file && !m_is_directory_entry) {
187 if(std::remove(m_output_filepath.c_str()) != 0) {
188 LOGE("Couldn't remove partial file! "
189 "std::remove(\"%s\") failed with errno:%s",
190 m_output_filepath.c_str(), strerror(errno));
197 if(m_close_unz_current_file) {
198 int err = unzCloseCurrentFile (m_owner.m_unzip);
200 LOGW("%s",getArchiveLogMessage(err, "unzCloseCurrentFile()").c_str());
205 void UnZipExtractRequest::getCurrentFileInfo()
208 int err = unzGetCurrentFileInfo(m_owner.m_unzip, &m_file_info,
209 m_filename_inzip, sizeof(m_filename_inzip), NULL, 0, NULL, 0);
211 LOGE("ret: %d", err);
212 throwArchiveException(err, "unzGetCurrentFileInfo()");
215 LOGD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
216 LOGD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
218 std::string file_path = m_filename_inzip;
219 if(!m_base_strip_path.empty()) {
220 if(file_path.find(m_base_strip_path) != 0) {
221 LOGW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
222 m_base_strip_path.c_str(),
226 file_path = file_path.substr(m_base_strip_path.length());
227 LOGD("Stripped file name: [%s]", file_path.c_str());
231 LOGD("Not stripped file name: [%s]", file_path.c_str());
234 m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
236 LOGD("Packed: [%s], uncompressed_size: %d, will extract to: [%s]",
237 m_filename_inzip, m_file_info.uncompressed_size,
238 m_output_filepath.c_str());
240 std::string path, name;
241 divideToPathAndName(file_path, path, name);
242 m_new_dir_path = removeDuplicatedSlashesFromPath(m_extract_path + "/" + path);
243 m_new_dir_status = getPathStatus(m_new_dir_path);
244 m_is_directory_entry = name.empty();
246 LOGD("New output dir: [%s] status: %d m_is_directory_entry: %d",
247 m_new_dir_path.c_str(), m_new_dir_status, m_is_directory_entry);
249 if(FPS_DIRECTORY != m_new_dir_status) {
251 if(m_is_directory_entry) {
253 std::string base_directories;
254 const size_t len = m_new_dir_path.length();
256 for(int i = static_cast<int>(len) - 2; i >= 0; i--) { //skip last \, /
257 const char& cur = m_new_dir_path[i];
258 if('\\' == cur || '/' == cur) {
259 base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
264 LOGD("Type: DIRECTORY checking base output directories: [%s]",
265 base_directories.c_str());
266 createMissingDirectories(base_directories, false);
268 LOGD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
269 createMissingDirectories(m_new_dir_path, false);
274 void UnZipExtractRequest::handleDirectoryEntry()
277 if(FPS_DIRECTORY != m_new_dir_status) {
279 if(FPS_FILE == m_new_dir_status) {
280 if(m_callback->getOverwrite()) { //Is a file & overwrite is set:
281 std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
282 if(std::remove(fn.c_str()) != 0) {
283 LOGE("std::remove(\"%s\") failed with errno:%s",
284 m_new_dir_path.c_str(), strerror(errno));
285 throw UnknownException(
286 "Could not overwrite file in output directory");
288 } else { //Is a file & overwrite is not set:
289 LOGE("Failed to extract directory, "
290 "file with the same name exists in output directory");
291 throw UnknownException("Failed to extract directory, "
292 "file with the same name exists in output directory");
296 //Try to create new directory in output directory
297 if(mkdir(m_new_dir_path.c_str(), 0775) == -1) {
298 LOGE("Couldn't create new directory: %s errno:%s",
299 m_new_dir_path.c_str(), strerror(errno));
300 throw UnknownException(
301 "Could not create new directory in extract output directory");
305 LOGD("Set dir: [%s] access and modify to: %4d-%2d-%2d %2d:%2d:%2d", m_new_dir_path.c_str(),
306 m_file_info.tmu_date.tm_year,
307 m_file_info.tmu_date.tm_mon,
308 m_file_info.tmu_date.tm_mday,
309 m_file_info.tmu_date.tm_hour,
310 m_file_info.tmu_date.tm_min,
311 m_file_info.tmu_date.tm_sec);
313 // Directory already exists we only need to update time
314 changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
316 LOGD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
319 bool UnZipExtractRequest::prepareOutputSubdirectory()
322 //This zip entry points to file - verify that parent directory in output dir exists
323 if(FPS_DIRECTORY != m_new_dir_status) {
324 if(FPS_FILE == m_new_dir_status) {
325 LOGE("Path: %s is pointing to file not directory!",
326 m_new_dir_path.c_str());
327 throw UnknownException("Failed to extract file from zip archive, "
328 "output path is invalid");
331 //Try to create new directory in output directory
332 //TODO investigate 0775 (mode) - Filesystem assumed that file should have parent mode
333 if(mkdir(m_new_dir_path.c_str(), 0775) == -1) {
334 LOGW("couldn't create new directory: %s errno:%s",
335 m_new_dir_path.c_str(), strerror(errno));
336 //TODO check why mkdir return -1 but directory is successfully created
337 // throw UnknownException(
338 // "Could not create new directory in extract output directory");
342 if(m_callback->isCanceled()) {
343 LOGD("Operation cancelled");
344 throw OperationCanceledException();
347 const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
348 if(FPS_NOT_EXIST != output_fstatus) {
349 if(!m_callback->getOverwrite()) {
350 LOGW("%s exists at output path: [%s], overwrite is set to FALSE",
351 (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"),
352 m_output_filepath.c_str());
354 //Just skip this file - TODO: this should be documented in WIDL
357 if(FPS_DIRECTORY == output_fstatus) {
359 Filesystem::PathPtr path = Filesystem::Path::create(m_output_filepath);
360 Filesystem::NodePtr node = Filesystem::Node::resolve(path);
361 node->remove(Filesystem::OPT_RECURSIVE);
362 LOGD("Removed directory: [%s]", m_output_filepath.c_str());
363 } catch(BasePlatformException& ex) {
364 LOGE("Remove dir: [%s] failed with exception: %s:%s",
365 m_output_filepath.c_str(),
366 ex.getName().c_str(), ex.getMessage().c_str());
367 throw UnknownException("Could not overwrite existing directory");
369 LOGE("Remove dir: [%s] failed", m_output_filepath.c_str());
370 throw UnknownException("Could not overwrite existing directory");
373 //We will overwrite it with fopen
381 void UnZipExtractRequest::handleFileEntry()
384 if(!prepareOutputSubdirectory()) {
385 LOGE("File exists but overwrite is false");
386 throw InvalidModificationException("file already exists.");
389 int err = unzOpenCurrentFilePassword(m_owner.m_unzip,
390 NULL); //password is not supported yet therefore passing NULL
392 LOGE("ret: %d", err);
393 throwArchiveException(err, "unzOpenCurrentFilePassword()");
396 //We have successfully opened curent file, therefore we should close it later
397 m_close_unz_current_file = true;
399 const size_t buffer_size = m_owner.m_default_buffer_size;
400 m_buffer = new(std::nothrow) char[buffer_size];
402 LOGE("Couldn't allocate buffer with size: %s",
403 bytesToReadableString(buffer_size).c_str());
404 throw UnknownException("Memory allocation failed");
407 m_output_file = fopen(m_output_filepath.c_str(), "wb");
409 LOGE("Couldn't open output file: %s", m_output_filepath.c_str());
410 throw UnknownException("Could not create extracted file");
412 m_delete_output_file = true;
415 bool marked_as_finished = false;
417 LOGD("Started extracting: [%s] uncompressed size: %d - %s", m_filename_inzip,
418 m_file_info.uncompressed_size,
419 bytesToReadableString(m_file_info.uncompressed_size).c_str());
421 ExtractAllProgressCallback* extract_callback = NULL;
422 if(m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
423 m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
424 extract_callback = static_cast<ExtractAllProgressCallback*>(m_callback);
425 extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
428 ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
429 auto it = entries->find(m_filename_inzip);
430 if (it == entries->end()) {
431 LOGE("Entry not found");
432 throw Common::NotFoundException("Entry not found");
436 if(m_callback->isCanceled()) {
437 LOGD("Operation cancelled");
438 throw OperationCanceledException();
441 read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
443 LOGE("unzReadCurrentFile failed with error code:%d for file:%s", read_size,
445 throw UnknownException("Failed to extract file from zip archive");
447 else if(0 == read_size) {
449 if(extract_callback) {
450 if(!marked_as_finished) {
451 LOGD("NOT marked_as_finished -> increment extracted files counter");
452 extract_callback->finishedExtractingFile();
454 //Call progress callback only if we expected empty file
455 if(m_file_info.uncompressed_size == 0) {
456 LOGD("Calling progress callback(%f, %s)",
457 extract_callback->getOverallProgress(), m_filename_inzip);
458 extract_callback->callProgressCallbackOnMainThread(
459 extract_callback->getOverallProgress(), it->second);
463 //Finished writing file, we should not delete extracted file
464 m_delete_output_file = false;
468 if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
469 LOGE("Couldn't write extracted data to output file:%s",
470 m_output_filepath.c_str());
471 throw UnknownException("Could not write extract file into output file");
474 if(extract_callback) {
475 extract_callback->extractedPartOfFile(read_size);
476 LOGD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
477 bytesToReadableString(read_size).c_str(),
478 100.0f * extract_callback->getCurrentFileProgress(),
479 100.0f * extract_callback->getOverallProgress());
481 // It is better to update number of extracted entries so we will have
482 // overal progres: 1.0 if all files are extracted
484 if(extract_callback->getCurrentFileProgress() >= 1.0) {
485 LOGD("Current file: [%s] progress: %f >= 1.0 -> "
486 "marked_as_finished = true and increment extracted files counter",
487 m_filename_inzip, extract_callback->getCurrentFileProgress());
488 marked_as_finished = true;
489 extract_callback->finishedExtractingFile();
492 LOGD("Calling progress callback(%f, %s)",
493 extract_callback->getOverallProgress(), m_filename_inzip);
494 extract_callback->callProgressCallbackOnMainThread(
495 extract_callback->getOverallProgress(), it->second);
500 fclose(m_output_file);
501 m_output_file = NULL;
504 changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
507 } //namespace Archive
508 } //namespace DeviceAPI