tizen 2.3.1 release
[framework/web/wearable/wrt-plugins-tizen.git] / src / Archive / ArchiveFile.cpp
1 //
2 // Tizen Web Device API
3 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 //
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17 #include "ArchiveFile.h"
18
19 #include <Logger.h>
20 #include <PlatformException.h>
21 #include <GlobalContextManager.h>
22 #include <Path.h>
23
24 #include "ArchiveManager.h"
25 #include "JSArchiveFileEntry.h"
26 #include "JSArchiveFile.h"
27 #include "ArchiveUtils.h"
28 #include "plugin_config_impl.h"
29 #include "UnZip.h"
30 #include "Zip.h"
31
32 namespace DeviceAPI {
33 namespace Archive {
34
35 using namespace Common;
36
37 Permission::Permission(bool r, bool w, bool rw, bool a){
38     permission[0] = r;
39     permission[1] = w;
40     permission[2] = rw;
41     permission[3] = a;
42 }
43
44 PermissionMap ArchiveFile::s_permission_map = {
45         {ARCHIVE_FUNCTION_API_ARCHIVE_FILE_ADD,
46                 Permission(NOT_ALLOWED, ALLOWED, ALLOWED, ALLOWED)},
47         {ARCHIVE_FUNCTION_API_ARCHIVE_FILE_EXTRACT_ALL,
48                 Permission(ALLOWED, NOT_ALLOWED, ALLOWED, NOT_ALLOWED)},
49         {ARCHIVE_FUNCTION_API_ARCHIVE_FILE_GET_ENTRIES,
50                 Permission(ALLOWED, NOT_ALLOWED, ALLOWED, NOT_ALLOWED)},
51         {ARCHIVE_FUNCTION_API_ARCHIVE_FILE_GET_ENTRY_BY_NAME,
52                 Permission(ALLOWED, NOT_ALLOWED, ALLOWED, NOT_ALLOWED)}
53 };
54
55 ArchiveFile::ArchiveFile() :
56         enable_shared_from_this<ArchiveFile>(),
57         m_decompressed_size(0),
58         m_is_open(false),
59         m_overwrite(false),
60         m_created_as_new_empty_archive(false)
61 {
62     LOGD("Entered");
63 }
64
65 ArchiveFile::ArchiveFile(FileMode file_mode) :
66         enable_shared_from_this<ArchiveFile>(),
67         m_decompressed_size(0),
68         m_is_open(false),
69         m_overwrite(false)
70 {
71     m_file_mode = file_mode;
72 }
73
74 ArchiveFile::~ArchiveFile()
75 {
76     LOGD("Entered");
77
78     if(m_entry_map) {
79         LOGD("Unlinking old m_entry_map: %d ArchiveFileEntries", m_entry_map->size());
80         for(auto it = m_entry_map->begin(); it != m_entry_map->end(); ++it) {
81             if(it->second) {
82                 it->second->setArchiveFileNonProtectPtr(NULL);
83             }
84         }
85     }
86 }
87
88 gboolean ArchiveFile::openTaskCompleteCB(void *data)
89 {
90     LOGD("Entered");
91     auto callback = static_cast<OperationCallbackData*>(data);
92     if (!callback) {
93         LOGE("callback is null");
94         return false;
95     }
96
97     JSContextRef context = callback->getContext();
98     if (!GlobalContextManager::getInstance()->isAliveGlobalContext(context)) {
99         LOGE("context was closed");
100         delete callback;
101         callback = NULL;
102         return false;
103     }
104     try {
105         if (callback->isError()) {
106             JSObjectRef errobj = JSWebAPIErrorFactory::makeErrorObject(context,
107                     callback->getErrorName(),
108                     callback->getErrorMessage());
109             callback->callErrorCallback(errobj);
110         }
111         else {
112             JSObjectRef result = JSArchiveFile::makeJSObject(context,
113                     callback->getArchiveFile());
114             callback->callSuccessCallback(result);
115         }
116     }
117     catch (const BasePlatformException &err) {
118         LOGE("%s (%s)", err.getName().c_str(), err.getMessage().c_str());
119     }
120     catch (...) {
121         LOGE("Unknown error occurs");
122     }
123
124     delete callback;
125     callback = NULL;
126
127     return false;
128 }
129
130 gboolean ArchiveFile::callErrorCallback(void* data)
131 {
132     LOGD("Entered");
133     auto callback = static_cast<OperationCallbackData*>(data);
134     if (!callback) {
135         LOGE("callback is null");
136         return false;
137     }
138
139     JSContextRef context = callback->getContext();
140     if (!GlobalContextManager::getInstance()->isAliveGlobalContext(context)) {
141         LOGE("context was closed");
142         delete callback;
143         callback = NULL;
144         return false;
145     }
146
147     try {
148         if (callback->isError()) {
149             JSObjectRef errobj = JSWebAPIErrorFactory::makeErrorObject(context,
150                     callback->getErrorName(),
151                     callback->getErrorMessage());
152             callback->callErrorCallback(errobj);
153         }
154         else {
155             LOGW("The success callback should be not be called in this case");
156         }
157     }
158     catch (const BasePlatformException &err) {
159         LOGE("%s (%s)", err.getName().c_str(), err.getMessage().c_str());
160     }
161     catch (...) {
162         LOGE("Unknown error occurs");
163     }
164
165     delete callback;
166     callback = NULL;
167
168     return false;
169 }
170
171 void* ArchiveFile::taskManagerThread(void *data)
172 {
173     LOGD("Entered");
174     ArchiveFileHolder* archive_file_holder = static_cast<ArchiveFileHolder*>(data);
175     if (!archive_file_holder) {
176         LOGE("archive_file_holder is null");
177         return NULL;
178     }
179
180     if (!archive_file_holder->ptr){
181         LOGE("archive_file is null");
182         delete archive_file_holder;
183         archive_file_holder = NULL;
184         return NULL;
185     }
186
187     while(true){
188         OperationCallbackData* callback = NULL;
189         bool call_error_callback = false;
190         try{
191             {
192                 std::lock_guard<std::mutex> lock(archive_file_holder->ptr->m_mutex);
193                 if(archive_file_holder->ptr->m_task_queue.empty()){
194                     break;
195                 }
196                 callback = archive_file_holder->ptr->m_task_queue.back().second;
197             }
198             if(callback && !callback->isCanceled()){
199                 callback->executeOperation(archive_file_holder->ptr);
200             }
201             {
202                 std::lock_guard<std::mutex> lock(archive_file_holder->ptr->m_mutex);
203                 archive_file_holder->ptr->m_task_queue.pop_back();
204             }
205         } catch (const OperationCanceledException &err) {
206             {
207                 std::lock_guard<std::mutex> lock(archive_file_holder->ptr->m_mutex);
208                 archive_file_holder->ptr->m_task_queue.pop_back();
209             }
210             delete callback;
211             callback = NULL;
212         } catch (const BasePlatformException &err) {
213             LOGE("taskManagerThread fails, %s: %s", err.getName().c_str(),
214                     err.getMessage().c_str());
215             callback->setError(err.getName().c_str(), err.getMessage().c_str());
216             call_error_callback = true;
217         } catch (...) {
218             LOGE("taskManagerThread fails");
219             callback->setError(JSWebAPIErrorFactory::UNKNOWN_ERROR, "UnknownError.");
220             call_error_callback = true;
221         }
222         if(call_error_callback) {
223             {
224                 std::lock_guard<std::mutex> lock(archive_file_holder->ptr->m_mutex);
225                 archive_file_holder->ptr->m_task_queue.pop_back();
226             }
227             if (!g_idle_add(callErrorCallback, static_cast<void*>(callback))) {
228                 LOGE("g_idle_add fails");
229                 delete callback;
230                 callback = NULL;
231             }
232         }
233     }
234
235     delete archive_file_holder;
236     archive_file_holder = NULL;
237
238     return NULL;
239 }
240
241 long ArchiveFile::addOperation(OperationCallbackData* callback)
242 {
243     LOGD("Entered callback type:%d", callback->getCallbackType());
244
245     const long operation_id =
246             ArchiveManager::getInstance().getNextOperationId(shared_from_this());
247     callback->setOperationId(operation_id);
248     callback->setArchiveFile(shared_from_this());
249     std::size_t size = 0;
250     {
251         std::lock_guard<std::mutex> lock(m_mutex);
252         m_task_queue.push_front(CallbackPair(operation_id, callback));
253         size = m_task_queue.size();
254     }
255     if(1 == size){
256         pthread_t thread;
257         ArchiveFileHolder* holder = new(std::nothrow) ArchiveFileHolder();
258         if(!holder){
259             LOGE("Memory allocation error");
260             throw UnknownException("Memory allocation error");
261         }
262         holder->ptr = shared_from_this();
263         if (pthread_create(&thread, NULL, taskManagerThread,
264                 static_cast<void*>(holder))) {
265             LOGE("Thread creation failed");
266             delete holder;
267             holder = NULL;
268             throw UnknownException("Thread creation failed");
269         }
270
271         if (pthread_detach(thread)) {
272             LOGE("Thread detachment failed");
273         }
274     }
275     return operation_id;
276 }
277
278 void ArchiveFile::extractAllTask(ExtractAllProgressCallback* callback)
279 {
280     Filesystem::FilePtr directory = callback->getDirectory();
281
282     if(!directory) {
283         LOGE("Directory is null");
284         throw UnknownException("Directory is null");
285     } else {
286         if(!directory->getNode()){
287             LOGE("Node in directory is null");
288             throw UnknownException("Node is null");
289         }
290     }
291
292     if(!m_file) {
293         LOGE("File is null");
294         throw UnknownException("File is null");
295     } else {
296         if(!m_file->getNode()){
297             LOGE("Node in file is null");
298             throw UnknownException("Node in file is null");
299         }
300     }
301
302     // For explanation please see:
303     //    ArchiveFile.h m_created_as_new_empty_archive description
304     //
305     if(m_file->getNode()->getSize() == 0) {
306         LOGD("Zip file: %s is empty",
307                 m_file->getNode()->getPath()->getFullPath().c_str());
308
309         if(m_created_as_new_empty_archive) {
310             //We do not call progress callback since we do not have any ArchiveFileEntry
311             callback->callSuccessCallbackOnMainThread();
312             callback = NULL;
313             return;
314         }
315         else {
316             LOGW("m_created_as_new_empty_archive is false");
317             LOGE("Throwing InvalidStateException: File is not valid ZIP archive");
318             throw InvalidStateException("File is not valid ZIP archive");
319         }
320     }
321
322     UnZipPtr unzip = createUnZipObject();
323     unzip->extractAllFilesTo(directory->getNode()->getPath()->getFullPath(), callback);
324 }
325
326 long ArchiveFile::getEntries(GetEntriesCallbackData* callback)
327 {
328     LOGD("Entered");
329     if(!callback) {
330         LOGE("callback is NULL");
331         throw UnknownException("Could not get list of files in archive");
332     }
333
334     throwInvalidStateErrorIfArchiveFileIsClosed();
335
336     return addOperation(callback);
337 }
338
339 gboolean ArchiveFile::getEntriesTaskCompleteCB(void *data)
340 {
341     auto callback = static_cast<GetEntriesCallbackData*>(data);
342     if (!callback) {
343         LOGE("callback is null");
344         return false;
345     }
346
347     JSContextRef context = callback->getContext();
348     if (!GlobalContextManager::getInstance()->isAliveGlobalContext(context)) {
349         LOGE("context was closed");
350         delete callback;
351         callback = NULL;
352         return false;
353     }
354
355     try {
356         ArchiveFileEntryPtrMapPtr entries = callback->getEntries();
357         unsigned int size = entries->size();
358
359         JSObjectRef objArray[size];
360         int i = 0;
361         for(auto it = entries->begin(); it != entries->end(); it++) {
362             objArray[i] = JSArchiveFileEntry::makeJSObject(context, it->second);
363             i++;
364         }
365
366         JSValueRef exception = NULL;
367         JSObjectRef jsResult = JSObjectMakeArray(context, size,
368                 size > 0 ? objArray : NULL, &exception);
369         if (exception != NULL) {
370             throw Common::UnknownException(context, exception);
371         }
372
373         callback->callSuccessCallback(jsResult);
374     }
375     catch (const BasePlatformException &err) {
376         LOGE("%s (%s)", err.getName().c_str(), err.getMessage().c_str());
377     }
378     catch (...) {
379         LOGE("Unknown error occurs");
380     }
381
382     delete callback;
383     callback = NULL;
384
385     return false;
386 }
387
388 long ArchiveFile::extractAll(ExtractAllProgressCallback *callback)
389 {
390     LOGD("Entered");
391     if(!callback) {
392         LOGE("callback is NULL");
393         throw UnknownException("Could not extract all files from archive");
394     }
395
396     throwInvalidStateErrorIfArchiveFileIsClosed();
397
398     return addOperation(callback);
399 }
400
401 long ArchiveFile::extractEntryTo(ExtractEntryProgressCallback* callback)
402 {
403     LOGD("Entered");
404     if(!callback) {
405         LOGE("callback is NULL");
406         throw UnknownException("Could not extract archive file entry");
407     }
408
409     // FIXME according to documentation:
410     // if archive was closed, any further operation attempt will make InvalidStateError
411     // but method extract() from ArchiveFileEntryObject is not permitted to throw above exception
412
413     // uncomment in case when this method have permission to throwing InvalidStateError
414     // throwInvalidStateErrorIfArchiveFileisClosed();
415     if(!m_is_open) {
416         LOGE("Archive is not opened");
417         throw UnknownException("Archive is not opened");
418     }
419
420     return addOperation(callback);
421 }
422
423
424 long ArchiveFile::add(AddProgressCallback *callback)
425 {
426     LOGD("Entered");
427     if(!callback) {
428         LOGE("callback is NULL");
429         throw UnknownException("Could not add file to archive");
430     }
431     if(FileMode::READ == m_file_mode) {
432         LOGE("Trying to add file when READ access mode selected");
433         throw InvalidAccessException("Add not allowed for \"r\" access mode");
434     }
435
436     throwInvalidStateErrorIfArchiveFileIsClosed();
437
438     return addOperation(callback);
439 }
440
441 void ArchiveFile::close()
442 {
443     LOGD("Entered");
444
445     if(!m_is_open){
446         LOGD("Archive already closed");
447     }
448     m_is_open = false;
449
450     return;
451 }
452
453 long ArchiveFile::getEntryByName(GetEntryByNameCallbackData* callback)
454 {
455     LOGD("Entered");
456     if(!callback) {
457         LOGE("callback is NULL");
458         throw UnknownException("Could not get archive file entries by name");
459     }
460
461     throwInvalidStateErrorIfArchiveFileIsClosed();
462
463     return addOperation(callback);
464 }
465
466 gboolean ArchiveFile::getEntryByNameTaskCompleteCB(void *data)
467 {
468     auto callback = static_cast<GetEntryByNameCallbackData*>(data);
469     if (!callback) {
470         LOGE("callback is null");
471         return false;
472     }
473
474     JSContextRef context = callback->getContext();
475     if (!GlobalContextManager::getInstance()->isAliveGlobalContext(context)) {
476         LOGE("context was closed");
477         delete callback;
478         callback = NULL;
479         return false;
480     }
481     try {
482         if (callback->isError()) {
483             JSObjectRef errobj = JSWebAPIErrorFactory::makeErrorObject(context,
484                     callback->getErrorName(),
485                     callback->getErrorMessage());
486             callback->callErrorCallback(errobj);
487         }
488         else {
489             JSObjectRef entry = JSArchiveFileEntry::makeJSObject(context,
490                     callback->getFileEntry());
491             callback->callSuccessCallback(entry);
492         }
493     }
494     catch (const BasePlatformException &err) {
495         LOGE("%s (%s)", err.getName().c_str(), err.getMessage().c_str());
496     }
497     catch (...) {
498         LOGE("Unknown error occurs");
499     }
500
501     delete callback;
502     callback = NULL;
503
504     return false;
505 }
506
507 Filesystem::FilePtr ArchiveFile::getFile() const
508 {
509     LOGD("Entered");
510     return m_file;
511 }
512
513 void ArchiveFile::setFile(Filesystem::FilePtr file)
514 {
515     LOGD("Entered");
516     m_file = file;
517 }
518
519 bool ArchiveFile::isOverwrite() const
520 {
521     return m_overwrite;
522 }
523
524 void ArchiveFile::setOverwrite(bool overwrite)
525 {
526     LOGD("Entered");
527     m_overwrite = overwrite;
528 }
529
530 unsigned long ArchiveFile::getDecompressedSize() const
531 {
532     LOGD("Entered");
533     return m_decompressed_size;
534 }
535
536 void ArchiveFile::setDecompressedSize(unsigned long decompressed_size)
537 {
538     LOGD("Entered");
539     m_decompressed_size = decompressed_size;
540 }
541
542 bool ArchiveFile::isOpen() const
543 {
544     LOGD("Entered");
545     return m_is_open;
546 }
547
548 void ArchiveFile::setIsOpen(bool is_open)
549 {
550     LOGD("Entered");
551     m_is_open = is_open;
552 }
553
554 ArchiveFileEntryPtrMapPtr ArchiveFile::getEntryMap() const
555 {
556     return m_entry_map;
557 }
558
559 void ArchiveFile::setEntryMap(ArchiveFileEntryPtrMapPtr entries)
560 {
561     LOGD("Entered");
562
563     if(m_entry_map) {
564         LOGD("Unlinking old m_entry_map: %d ArchiveFileEntries", m_entry_map->size());
565         for(auto it = m_entry_map->begin(); it != m_entry_map->end(); ++it) {
566             if(it->second) {
567                 it->second->setArchiveFileNonProtectPtr(NULL);
568             }
569         }
570     }
571
572     m_entry_map = entries;
573
574     LOGD("Linking new m_entry_map ArchiveFileEntries (%d) with ArchiveFile object",
575             m_entry_map->size());
576     for(auto it = m_entry_map->begin(); it != m_entry_map->end(); ++it) {
577         if(it->second) {
578             it->second->setArchiveFileNonProtectPtr(this);
579         }
580     }
581 }
582
583 UnZipPtr ArchiveFile::createUnZipObject()
584 {
585     LOGD("Entered");
586     if(!m_is_open) {
587         LOGE("File is not opened");
588         throw UnknownException("File is not opened");
589     }
590
591     if (!m_file) {
592         LOGE("m_file is null");
593         throw UnknownException("File is null");
594     }
595
596     Filesystem::NodePtr node = m_file->getNode();
597     if(!node) {
598         LOGE("Node is null");
599         throw UnknownException("Node is null");
600     }
601
602     UnZipPtr unzip = UnZip::open(node->getPath()->getFullPath());
603     return unzip;
604 }
605
606 ZipPtr ArchiveFile::createZipObject()
607 {
608     LOGD("Entered");
609     if(!m_is_open) {
610         LOGE("File is not opened");
611         throw UnknownException("File is not opened");
612     }
613
614     if (!m_file) {
615         LOGE("m_file is null");
616         throw UnknownException("File is null");
617     }
618
619     Filesystem::NodePtr node = m_file->getNode();
620     if(!node) {
621         LOGE("Node is null");
622         throw UnknownException("Node is null");
623     }
624
625     ZipPtr zip = Zip::open(node->getPath()->getFullPath());
626     return zip;
627
628 }
629
630 bool ArchiveFile::isAllowedOperation(const std::string& method_name)
631 {
632     LOGD("Entered");
633     PermissionMap::iterator it = s_permission_map.find(method_name);
634     if (it != s_permission_map.end()) {
635         return it->second.permission[m_file_mode];
636     }
637     return false;
638 }
639
640 FileMode ArchiveFile::getFileMode() const
641 {
642    LOGD("Entered");
643    return m_file_mode;
644 }
645
646 void ArchiveFile::setFileMode(FileMode file_mode)
647 {
648     LOGD("Entered");
649     m_file_mode = file_mode;
650 }
651
652 void ArchiveFile::throwInvalidStateErrorIfArchiveFileIsClosed() const
653 {
654     if(!m_is_open){
655         LOGE("ArchiveFile closed - operation not permitted");
656         throw InvalidStateException(
657             "ArchiveFile closed - operation not permitted");
658     }
659 }
660
661 void ArchiveFile::setCreatedAsNewEmptyArchive(bool new_and_empty)
662 {
663     m_created_as_new_empty_archive = new_and_empty;
664 }
665
666 bool ArchiveFile::isCreatedAsNewEmptyArchive() const
667 {
668     return m_created_as_new_empty_archive;
669 }
670
671 void ArchiveFile::updateListOfEntries()
672 {
673     // For explanation please see:
674     //    ArchiveFile.h m_created_as_new_empty_archive description
675     //
676     if(m_file->getNode()->getSize() == 0) {
677         LOGD("Zip file: %s is empty",
678                 m_file->getNode()->getPath()->getFullPath().c_str());
679
680         if(m_created_as_new_empty_archive) {
681             LOGD("OK this is empty archive = nothing to do yet");
682             return;
683         }
684         else {
685             LOGW("m_created_as_new_empty_archive is false");
686             LOGE("Throwing InvalidStateException: File is not valid ZIP archive");
687             throw InvalidStateException("File is not valid ZIP archive");
688         }
689     }
690
691     UnZipPtr unzip = createUnZipObject();
692     unsigned long decompressedSize = 0;
693     ArchiveFileEntryPtrMapPtr emap = unzip->listEntries(&decompressedSize);
694     setEntryMap(emap);
695     setDecompressedSize(decompressedSize);
696 }
697
698 bool ArchiveFile::isEntryWithNameInArchive(const std::string& name_in_zip,
699         bool* out_is_directory,
700         std::string* out_matching_name)
701 {
702     if(!m_entry_map) {
703         LOGW("m_entry_map is NULL");
704         return false;
705     }
706
707     const bool name_in_zip_is_dir = isDirectoryPath(name_in_zip);
708     bool set_is_directory = false;
709     bool set_name_exists = false;
710
711     //Try exact name:
712     auto it = m_entry_map->find(name_in_zip);
713     if(it != m_entry_map->end()) {
714         set_is_directory = name_in_zip_is_dir;
715         set_name_exists = true;
716     }
717     else {
718         if(name_in_zip_is_dir) {
719             //If name_in_zip is pointing at directory try file
720             it = m_entry_map->find(removeTrailingDirectorySlashFromPath(name_in_zip));
721             set_is_directory = false;
722         } else {
723             //If name_in_zip is pointing at file try directory
724             it = m_entry_map->find(name_in_zip + "/");
725             set_is_directory = true;
726         }
727
728         if(it != m_entry_map->end()) {
729             set_name_exists = true;
730         }
731     }
732
733     if(!set_name_exists) {
734         return false;
735     }
736
737     if(out_is_directory) {
738         *out_is_directory = set_is_directory;
739     }
740     if(out_matching_name) {
741         *out_matching_name = it->first;
742     }
743
744     return true;
745 }
746
747 } // Archive
748 } // DeviceAPI