From: Jin-Woo Jeong Date: Sat, 14 Feb 2015 08:14:23 +0000 (+0900) Subject: [Download] module implementation X-Git-Tag: submit/tizen_tv/20150603.064601~1^2~435 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=159d713d3601451462d225002384fad09e9dc1e8;p=platform%2Fcore%2Fapi%2Fwebapi-plugins.git [Download] module implementation Validation: - Own sample app (basic functionality test) - tct : 100% (62/62) Change-Id: Iee69ab930b5a81772b69c5c736b78bae9daa00f3 --- diff --git a/packaging/webapi-plugins.spec b/packaging/webapi-plugins.spec index 1fe9c5cf..ea5203a3 100644 --- a/packaging/webapi-plugins.spec +++ b/packaging/webapi-plugins.spec @@ -31,7 +31,7 @@ Source0: %{name}-%{version}.tar.gz %define tizen_feature_core_api_support 0 %define tizen_feature_datacontrol_support 1 %define tizen_feature_datasync_support 1 -%define tizen_feature_download_support 0 +%define tizen_feature_download_support 1 %define tizen_feature_exif_support 0 %define tizen_feature_fm_radio_support 0 %define tizen_feature_gamepad_support 0 @@ -195,6 +195,10 @@ BuildRequires: pkgconfig(accounts-svc) BuildRequires: pkgconfig(capi-data-control) %endif +%if 0%{?tizen_feature_download_support} +BuildRequires: pkgconfig(capi-web-url-download) +%endif + %if 0%{?tizen_feature_power_support} BuildRequires: pkgconfig(capi-system-power) %endif diff --git a/src/download/download.gyp b/src/download/download.gyp new file mode 100644 index 00000000..fbc669b0 --- /dev/null +++ b/src/download/download.gyp @@ -0,0 +1,28 @@ +{ + 'includes':[ + '../common/common.gypi', + ], + 'targets': [ + { + 'target_name': 'tizen_download', + 'type': 'loadable_module', + 'sources': [ + 'download_api.js', + 'download_extension.cc', + 'download_extension.h', + 'download_instance.cc', + 'download_instance.h', + ], + 'conditions': [ + ['tizen == 1', { + 'variables': { + 'packages': [ + 'capi-web-url-download', + 'capi-system-info' + ] + }, + }], + ], + }, + ], +} diff --git a/src/download/download_api.js b/src/download/download_api.js new file mode 100644 index 00000000..ff9869de --- /dev/null +++ b/src/download/download_api.js @@ -0,0 +1,298 @@ +// Download + +var validator_ = xwalk.utils.validator; +var types_ = validator_.Types; +var check_ = xwalk.utils.type; + + +var callbackId = 0; +var callbacks = {}; +var requests = {}; + + +extension.setMessageListener(function(json) { + + var result = JSON.parse(json); + var callback = callbacks[result['callbackId']]; + //console.log("PostMessage received: " + result.status); + + if (result.status == 'progress') { + var receivedSize = result.receivedSize; + var totalSize = result.totalSize; + callback.onprogress(result.callbackId, receivedSize, totalSize); + } + else if (result.status == 'paused') { + callback.onpaused(result.callbackId); + } + else if (result.status == 'canceled') { + callback.oncanceled(result.callbackId); + } + else if (result.status == 'completed') { + var fullPath = result.fullPath; + callback.oncompleted(result.callbackId, fullPath); + } + else if (result.status == 'error') { + callback.onfailed( + result.callbackId, new tizen.WebAPIError(result['error'].name, result['error'].message)); + } +}); + +function nextCallbackId() { + return callbackId++; +} + +function callNative(cmd, args) { + var json = {'cmd': cmd, 'args': args}; + var argjson = JSON.stringify(json); + var resultString = extension.internal.sendSyncMessage(argjson); + var result = JSON.parse(resultString); + + if (typeof result !== 'object') { + throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR); + } + + if (result['status'] == 'success') { + if (result['result']) { + return result['result']; + } + return true; + } else if (result['status'] == 'error') { + var err = result['error']; + if (err) { + throw new tizen.WebAPIException(err.name, err.message); + } + return false; + } +} + + +function callNativeWithCallback(cmd, args, callback) { + if (callback) { + var id = nextCallbackId(); + args['callbackId'] = id; + callbacks[id] = callback; + } + + return callNative(cmd, args); +} + +function SetReadOnlyProperty(obj, n, v) { + Object.defineProperty(obj, n, {value: v, writable: false}); +} + +var DownloadState = { + 'QUEUED': 'QUEUED', + 'DOWNLOADING': 'DOWNLOADING', + 'PAUSED': 'PAUSED', + 'CANCELED': 'CANCELED', + 'COMPLETED': 'COMPLETED', + 'FAILED': 'FAILED' +}; + +var DownloadNetworkType = { + 'CELLULAR': 'CELLULAR', + 'WIFI': 'WIFI', + 'ALL': 'ALL' +}; + +tizen.DownloadRequest = function(url, destination, fileName, networkType, httpHeader) { + validator_.isConstructorCall(this, tizen.DownloadRequest); + var args = validator_.validateArgs(arguments, [ + {'name' : 'url', 'type': types_.STRING, 'nullable': false, 'optional': false}, + {'name' : 'destination', 'type': types_.STRING, 'nullable': true, 'optional': true}, + {'name' : 'fileName', 'type': types_.STRING, 'nullable': true, 'optional': true}, + {'name' : 'networkType', 'type': types_.ENUM, 'values': ['CELLULAR', 'WIFI', 'ALL'], + 'nullable' : true, 'optional': true}, + {'name' : 'httpHeader', 'type': types_.Dictionary, 'nullable': true, 'optional': true} + ]); + + var url_ = url; + var networkType_; + + if (networkType === undefined) networkType_ = 'ALL'; + else if (networkType in DownloadNetworkType) networkType_ = networkType; + + Object.defineProperties(this, { + 'url': { enumerable: true, + get: function() { return url_;}, + set: function(value) { if (value != null) { url_ = value; }} }, + 'destination': { writable: true, enumerable: true, + value: destination === undefined ? '' : destination }, + 'fileName': { writable: true, enumerable: true, + value: fileName === undefined ? '' : fileName }, + 'networkType': { enumerable: true, + get: function() { return networkType_;}, + set: function(value) { + if (value === null || value in DownloadNetworkType) { networkType_ = value; }} }, + 'httpHeader': { writable: true, enumerable: true, + value: httpHeader === undefined ? {} : httpHeader } + }); +}; + + +function DownloadManager() { + // constructor of DownloadManager +} + +DownloadManager.prototype.start = function(downloadRequest) { + var args = validator_.validateArgs(arguments, [ + {'name' : 'downloadRequest', 'type': types_.PLATFORM_OBJECT, 'values': tizen.DownloadRequest}, + {'name' : 'downloadCallback', 'type': types_.LISTENER, + 'values' : ['onprogress', 'onpaused', 'oncanceled', 'oncompleted', 'onfailed'], + optional: true, nullable: true} + ]); + + var nativeParam = { + 'url': args.downloadRequest.url, + 'destination': args.downloadRequest.destination, + 'fileName': args.downloadRequest.fileName, + 'networkType': args.downloadRequest.networkType, + 'httpHeader': args.downloadRequest.httpHeader, + 'callbackId': nextCallbackId() + }; + + if (args.downloadCallback) { + this.setListener(nativeParam.callbackId, args.downloadCallback); + } + + try { + var syncResult = callNative('DownloadManager_start', nativeParam); + } catch (e) { + throw e; + } + + requests[nativeParam.callbackId] = args.downloadRequest; + + return nativeParam.callbackId; +}; + +DownloadManager.prototype.cancel = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {name: 'downloadId', type: types_.LONG, 'nullable': false, 'optional': false} + ]); + + var nativeParam = { + 'downloadId': args.downloadId + }; + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + try { + var syncResult = callNative('DownloadManager_cancel', nativeParam); + } catch (e) { + throw e; + } +}; + +DownloadManager.prototype.pause = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {'name': 'downloadId', 'type': types_.LONG, 'nullable': false, 'optional': false} + ]); + + var nativeParam = { + 'downloadId': args.downloadId + }; + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + try { + var syncResult = callNative('DownloadManager_pause', nativeParam); + } catch (e) { + throw e; + } +}; + +DownloadManager.prototype.resume = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {'name' : 'downloadId', 'type': types_.LONG, 'nullable': false, 'optional': false} + ]); + + var nativeParam = { + 'downloadId': args.downloadId + }; + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + try { + var syncResult = callNative('DownloadManager_resume', nativeParam); + } catch (e) { + throw e; + } +}; + +DownloadManager.prototype.getState = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {'name' : 'downloadId', 'type': types_.LONG, 'nullable': false, 'optional': false} + ]); + + var nativeParam = { + 'downloadId': args.downloadId + }; + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + try { + var syncResult = callNative('DownloadManager_getState', nativeParam); + } catch (e) { + throw e; + } + + return syncResult; +}; + +DownloadManager.prototype.getDownloadRequest = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {'name': 'downloadId', 'type': types_.LONG, 'nullable': false, 'optional': false} + ]); + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + return requests[args.downloadId]; +}; + +DownloadManager.prototype.getMIMEType = function(downloadId) { + var args = validator_.validateArgs(arguments, [ + {'name' : 'downloadId', 'type': types_.LONG, 'nullable': false, 'optional': false} + ]); + + var nativeParam = { + 'downloadId': args.downloadId + }; + + if (typeof requests[downloadId] === 'undefined') + throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR, + 'the identifier does not match any download operation in progress'); + + try { + var syncResult = callNative('DownloadManager_getMIMEType', nativeParam); + } catch (e) { + throw e; + } + + return syncResult; +}; + +DownloadManager.prototype.setListener = function(downloadId, downloadCallback) { + var args = validator_.validateArgs(arguments, [ + {'name' : 'downloadId', 'type': types_.LONG}, + {'name' : 'downloadCallback', 'type': types_.LISTENER, + 'values' : ['onprogress', 'onpaused', 'oncanceled', 'oncompleted', 'onfailed']} + ]); + + callbacks[args.downloadId] = args.downloadCallback; +}; + + + +exports = new DownloadManager(); + diff --git a/src/download/download_extension.cc b/src/download/download_extension.cc new file mode 100644 index 00000000..465536d6 --- /dev/null +++ b/src/download/download_extension.cc @@ -0,0 +1,31 @@ +// Copyright 2014 Samsung Electronics Co, Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "download/download_extension.h" + +#include "download/download_instance.h" + +// This will be generated from download_api.js +extern const char kSource_download_api[]; + +common::Extension* CreateExtension() { + return new DownloadExtension; +} + +DownloadExtension::DownloadExtension() { + SetExtensionName("tizen.download"); + SetJavaScriptAPI(kSource_download_api); + + const char* entry_points[] = { + "tizen.DownloadRequest", + NULL + }; + SetExtraJSEntryPoints(entry_points); +} + +DownloadExtension::~DownloadExtension() {} + +common::Instance* DownloadExtension::CreateInstance() { + return new extension::download::DownloadInstance; +} diff --git a/src/download/download_extension.h b/src/download/download_extension.h new file mode 100644 index 00000000..2d5b2432 --- /dev/null +++ b/src/download/download_extension.h @@ -0,0 +1,19 @@ +// Copyright 2014 Samsung Electronics Co, Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DOWNLOAD_DOWNLOAD_EXTENSION_H_ +#define DOWNLOAD_DOWNLOAD_EXTENSION_H_ + +#include "common/extension.h" + +class DownloadExtension : public common::Extension { + public: + DownloadExtension(); + virtual ~DownloadExtension(); + + private: + virtual common::Instance* CreateInstance(); +}; + +#endif // DOWNLOAD_DOWNLOAD_EXTENSION_H_ diff --git a/src/download/download_instance.cc b/src/download/download_instance.cc new file mode 100644 index 00000000..04640c77 --- /dev/null +++ b/src/download/download_instance.cc @@ -0,0 +1,705 @@ +// Copyright 2014 Samsung Electronics Co, Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "download/download_instance.h" +#include + +#include "common/picojson.h" +#include "common/logger.h" +#include "common/platform_exception.h" +#include "common/typeutil.h" + + +namespace extension { +namespace download { + +namespace { +// The privileges that required in Download API +const std::string kPrivilegeDownload = ""; + +} // namespace + +using common::NotFoundException; +using common::UnknownException; +using common::NetworkException; +using common::SecurityException; +using common::QuotaExceededException; +using common::NotSupportedException; +using common::InvalidStateException; +using common::IOException; +using common::InvalidValuesException; +using common::ServiceNotAvailableException; +using common::TypeMismatchException; + +DownloadInstance::DownloadInstance() { + using std::placeholders::_1; + using std::placeholders::_2; + #define REGISTER_SYNC(c, x) \ + RegisterSyncHandler(c, std::bind(&DownloadInstance::x, this, _1, _2)); + REGISTER_SYNC("DownloadManager_pause", DownloadManagerPause); + REGISTER_SYNC + ("DownloadManager_getDownloadRequest", DownloadManagerGetdownloadrequest); + REGISTER_SYNC("DownloadManager_setListener", DownloadManagerSetlistener); + REGISTER_SYNC("DownloadManager_getMIMEType", DownloadManagerGetmimetype); + REGISTER_SYNC("DownloadManager_start", DownloadManagerStart); + REGISTER_SYNC("DownloadManager_cancel", DownloadManagerCancel); + REGISTER_SYNC("DownloadManager_resume", DownloadManagerResume); + REGISTER_SYNC("DownloadManager_getState", DownloadManagerGetstate); + #undef REGISTER_SYNC +} + +DownloadInstance::~DownloadInstance() { + for (DownloadCallbackVector::iterator it = downCbVector.begin(); + it != downCbVector.end(); it++) { + delete (*it); + } +} + +#define CHECK_EXIST(args, name, out) \ + if (!args.contains(name)) {\ + ReportError(TypeMismatchException(name" is required argument"), out);\ + return;\ + } + +void DownloadInstance::OnStateChanged(int download_id, + download_state_e state, void* user_data) { + DownloadCallback* downCbPtr = static_cast(user_data); + + downCbPtr->state = state; + downCbPtr->downloadId = download_id; + + LoggerD("State for callbackId %d changed to %d", + downCbPtr->callbackId, static_cast(state)); + + switch (state) { + case DOWNLOAD_STATE_DOWNLOADING: + OnStart(download_id, user_data); + break; + case DOWNLOAD_STATE_PAUSED: + g_idle_add(OnPaused, downCbPtr); + break; + case DOWNLOAD_STATE_COMPLETED: + g_idle_add(OnFinished, downCbPtr); + break; + case DOWNLOAD_STATE_CANCELED: + g_idle_add(OnCanceled, downCbPtr); + break; + case DOWNLOAD_STATE_FAILED: + g_idle_add(OnFailed, downCbPtr); + break; + } +} + +gboolean DownloadInstance::OnProgressChanged(void* user_data) { + DownloadCallback* downCbPtr = static_cast(user_data); + DownloadInfoPtr diPtr = downCbPtr->instance->diMap[downCbPtr->callbackId]; + + picojson::value::object out; + out["status"] = picojson::value("progress"); + out["callbackId"] = + picojson::value(static_cast(downCbPtr->callbackId)); + out["receivedSize"] = + picojson::value(static_cast(downCbPtr->received)); + out["totalSize"] = picojson::value(static_cast(diPtr->file_size)); + + LoggerD("OnProgressChanged for callbackId %d Called: Received: %ld", + downCbPtr->callbackId, downCbPtr->received); + + picojson::value v = picojson::value(out); + downCbPtr->instance->PostMessage(v.serialize().c_str()); + + return FALSE; +} + +void DownloadInstance::OnStart(int download_id, void* user_data) { + unsigned long long totalSize; + int ret; + + DownloadCallback* downCbPtr = static_cast(user_data); + + LoggerD("OnStart for callbackId %d Called", downCbPtr->callbackId); + + DownloadInfoPtr diPtr = downCbPtr->instance->diMap[downCbPtr->callbackId]; + + download_get_content_size(download_id, &totalSize); + + diPtr->file_size = totalSize; +} + +gboolean DownloadInstance::OnFinished(void* user_data) { + char* fullPath = NULL; + + DownloadCallback* downCbPtr = static_cast(user_data); + DownloadInfoPtr diPtr = downCbPtr->instance->diMap[downCbPtr->callbackId]; + + LoggerD("OnFinished for callbackID %d Called", downCbPtr->callbackId); + + download_get_downloaded_file_path(downCbPtr->downloadId, &fullPath); + + download_unset_state_changed_cb(diPtr->download_id); + download_unset_progress_cb(diPtr->download_id); + download_destroy(diPtr->download_id); + + picojson::value::object out; + out["status"] = picojson::value("completed"); + out["callbackId"] = + picojson::value(static_cast(downCbPtr->callbackId)); + out["fullPath"] = picojson::value(fullPath); + + downCbPtr->instance->PostMessage(picojson::value(out).serialize().c_str()); + return FALSE; +} + +gboolean DownloadInstance::OnPaused(void* user_data) { + DownloadCallback* downCbPtr = static_cast(user_data); + DownloadInfoPtr diPtr = downCbPtr->instance->diMap[downCbPtr->callbackId]; + + LoggerD("OnPaused for callbackID %d Called", downCbPtr->callbackId); + + picojson::value::object out; + out["status"] = picojson::value("paused"); + out["callbackId"] = + picojson::value(static_cast(downCbPtr->callbackId)); + + downCbPtr->instance->PostMessage(picojson::value(out).serialize().c_str()); + return FALSE; +} + +gboolean DownloadInstance::OnCanceled(void* user_data) { + DownloadCallback* downCbPtr = static_cast(user_data); + DownloadInfoPtr diPtr = downCbPtr->instance->diMap[downCbPtr->callbackId]; + + LoggerD("OnCanceled for callbackID %d Called", downCbPtr->callbackId); + + download_unset_state_changed_cb(diPtr->download_id); + download_unset_progress_cb(diPtr->download_id); + download_destroy(diPtr->download_id); + + picojson::value::object out; + out["status"] = picojson::value("canceled"); + out["callbackId"] = + picojson::value(static_cast(downCbPtr->callbackId)); + + downCbPtr->instance->PostMessage(picojson::value(out).serialize().c_str()); + return FALSE; +} + +gboolean DownloadInstance::OnFailed(void* user_data) { + download_error_e error; + picojson::object out; + + DownloadCallback* downCbPtr = static_cast(user_data); + DownloadInstance* instance = downCbPtr->instance; + + download_get_error(downCbPtr->downloadId, &error); + + switch (error) { + case DOWNLOAD_ERROR_INVALID_PARAMETER: + instance->ReportError(NotFoundException("not found"), out); + break; + case DOWNLOAD_ERROR_OUT_OF_MEMORY: + instance->ReportError(UnknownException("Out of memory"), out); + break; + case DOWNLOAD_ERROR_NETWORK_UNREACHABLE: + instance->ReportError(NetworkException("Network is unreachable"), out); + break; + case DOWNLOAD_ERROR_CONNECTION_TIMED_OUT: + instance->ReportError(NetworkException("HTTP session timeout"), out); + break; + case DOWNLOAD_ERROR_NO_SPACE: + instance->ReportError(QuotaExceededException( + "No space left on device"), out); + break; + case DOWNLOAD_ERROR_PERMISSION_DENIED: + instance->ReportError(SecurityException( + "The application does not have the privilege to call this method."), + out); + break; + case DOWNLOAD_ERROR_NOT_SUPPORTED: + instance->ReportError(NotSupportedException("Not supported"), out); + break; + case DOWNLOAD_ERROR_INVALID_STATE: + instance->ReportError(InvalidStateException("Invalid state"), out); + break; + case DOWNLOAD_ERROR_CONNECTION_FAILED: + instance->ReportError(NetworkException("Connection failed"), out); + break; + case DOWNLOAD_ERROR_INVALID_URL: + instance->ReportError(InvalidValuesException("Invalid URL"), out); + break; + case DOWNLOAD_ERROR_INVALID_DESTINATION: + instance->ReportError(InvalidValuesException( + "Invalid destination"), out); + break; + case DOWNLOAD_ERROR_TOO_MANY_DOWNLOADS: + instance->ReportError(QuotaExceededException( + "Too many simultaneous downloads"), out); + break; + case DOWNLOAD_ERROR_QUEUE_FULL: + instance->ReportError(QuotaExceededException( + "Download server queue is full"), out); + break; + case DOWNLOAD_ERROR_ALREADY_COMPLETED: + instance->ReportError(InvalidStateException( + "The download is already completed"), out); + break; + case DOWNLOAD_ERROR_FILE_ALREADY_EXISTS: + instance->ReportError(IOException( + "Failed to rename the downloaded file"), out); + break; + case DOWNLOAD_ERROR_CANNOT_RESUME: + instance->ReportError(NotSupportedException("Cannot resume"), out); + break; + case DOWNLOAD_ERROR_FIELD_NOT_FOUND: + instance->ReportError(NotFoundException( + "Specified field not found"), out); + break; + case DOWNLOAD_ERROR_TOO_MANY_REDIRECTS: + instance->ReportError(NetworkException( + "Too many redirects from HTTP response header"), out); + break; + case DOWNLOAD_ERROR_UNHANDLED_HTTP_CODE: + instance->ReportError(NetworkException( + "The download cannot handle the HTTP status value"), out); + break; + case DOWNLOAD_ERROR_REQUEST_TIMEOUT: + instance->ReportError(NetworkException( + "No action after client creates a download ID"), out); + break; + case DOWNLOAD_ERROR_RESPONSE_TIMEOUT: + instance->ReportError(NetworkException( + "No call to start API for some time although the download is created"), + out); + break; + case DOWNLOAD_ERROR_SYSTEM_DOWN: + instance->ReportError(ServiceNotAvailableException( + "No response from client after rebooting download daemon"), out); + break; + case DOWNLOAD_ERROR_ID_NOT_FOUND: + instance->ReportError(NotFoundException( + "Download ID does not exist in download service module"), out); + break; + case DOWNLOAD_ERROR_INVALID_NETWORK_TYPE: + instance->ReportError(InvalidValuesException( + "Network bonding is set but network type is not set as ALL"), out); + break; + case DOWNLOAD_ERROR_NO_DATA: + instance->ReportError(NotFoundException( + "No data because the set API is not called"), out); + break; + case DOWNLOAD_ERROR_IO_ERROR: + instance->ReportError(IOException("Internal I/O error"), out); + break; + } + + out["callbackId"] = + picojson::value(static_cast(downCbPtr->callbackId)); + + downCbPtr->instance->PostMessage(picojson::value(out).serialize().c_str()); + return FALSE; +} + +void DownloadInstance::progress_changed_cb + (int download_id, long long unsigned received, void* user_data) { + DownloadCallback* downCbPtr = static_cast(user_data); + downCbPtr->received = received; + downCbPtr->downloadId = download_id; + + g_idle_add(OnProgressChanged, downCbPtr); +} + +void DownloadInstance::DownloadManagerStart + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "callbackId", out) + + int ret, downlodId; + std::string networkType; + + DownloadInfoPtr diPtr(new DownloadInfo); + + diPtr->callbackId = static_cast(args.get("callbackId").get()); + diPtr->url = args.get("url").get(); + + if (!args.get("destination").is()) { + if (args.get("destination").get() != "") { + diPtr->destination = args.get("destination").get(); + // need to use filesystem API + } + } + + LoggerD("destination: %s", diPtr->destination.c_str()); + + if (!args.get("fileName").is()) { + if (args.get("fileName").get() != "") { + diPtr->file_name = args.get("fileName").get(); + } + } + + if (!args.get("networkType").is()) { + networkType = args.get("networkType").get(); + } + + bool networkSupport; + + if (networkType == "CELLULAR") { + system_info_get_platform_bool( + "http://tizen.org/feature/network.telephony", &networkSupport); + if (!networkSupport) { + ReportError(NotSupportedException( + "The networkType of the given DownloadRequest" \ + "is not supported on a device."), out); + return; + } + diPtr->network_type = DOWNLOAD_NETWORK_DATA_NETWORK; + } else if (networkType == "WIFI") { + system_info_get_platform_bool( + "http://tizen.org/feature/network.wifi", &networkSupport); + if (!networkSupport) { + ReportError(NotSupportedException( + "The networkType of the given DownloadRequest" \ + "is not supported on a device."), out); + return; + } + diPtr->network_type = DOWNLOAD_NETWORK_WIFI; + } else if (networkType == "ALL") { + diPtr->network_type = DOWNLOAD_NETWORK_ALL; + } else { + ReportError( + InvalidValuesException( + "The input parameter contains an invalid network type."), out); + return; + } + + DownloadCallback* downCbPtr(new DownloadCallback); + + downCbPtr->callbackId = diPtr->callbackId; + downCbPtr->instance = this; + + downCbVector.push_back(downCbPtr); + LoggerD("Checking callback ID: %d", downCbPtr->callbackId); + + ret = download_create(&diPtr->download_id); + ret = + download_set_state_changed_cb + (diPtr->download_id, OnStateChanged, static_cast(downCbPtr)); + ret = + download_set_progress_cb + (diPtr->download_id, progress_changed_cb, static_cast(downCbPtr)); + ret = + download_set_url(diPtr->download_id, diPtr->url.c_str()); + + const char* dest; + + if (diPtr->destination == "Downloads") { + dest = "/opt/usr/media/Downloads"; // ret = download_set_destination(diPtr->download_id, diPtr->destination.c_str()); + ret = download_set_destination(diPtr->download_id, dest); + } + + if (!diPtr->file_name.empty()) { + ret = download_set_file_name(diPtr->download_id, diPtr->file_name.c_str()); + } + + ret = download_set_network_type(diPtr->download_id, diPtr->network_type); + + if (args.get("httpHeader").is()) { + picojson::object obj = args.get("httpHeader").get(); + for (picojson::object::const_iterator it = obj.begin(); + it != obj.end(); ++it) { + download_add_http_header_field + (diPtr->download_id, it->first.c_str(), it->second.to_str().c_str()); + } + } + + char* gUrl = NULL; + char* gDest = NULL; + char* gFilename = NULL; + download_network_type_e gNetType; + char** fields; + char* header_value; + int header_length; + + download_get_url(diPtr->download_id, &gUrl); + download_get_destination(diPtr->download_id, &gDest); + download_get_file_name(diPtr->download_id, &gFilename); + download_get_network_type(diPtr->download_id, &gNetType); + download_get_http_header_field_list + (diPtr->download_id, &fields, &header_length); + + for (int i = 0; i < header_length; i++) { + download_get_http_header_field + (diPtr->download_id, fields[i], &header_value); + LoggerD("HTTP HEADER %d: %s - %s", i, fields[i], header_value); + } + + LoggerD("Download Request Received" \ + "(URL: %s, Destination: %s, File name: %s, Network type: %d ", + gUrl, gDest, gFilename, static_cast(gNetType)); + + diMap[downCbPtr->callbackId] = diPtr; + + ret = download_start(diPtr->download_id); + + if (ret == DOWNLOAD_ERROR_NONE) + ReportSuccess(out); + else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) + ReportError(InvalidValuesException + ("The input parameter contains an invalid value."), out); + else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) + ReportError(UnknownException("Out of memory"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_STATE) + ReportError(InvalidValuesException("Invalid state"), out); + else if (ret == DOWNLOAD_ERROR_IO_ERROR) + ReportError(UnknownException("Internal I/O error"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_URL) + ReportError(InvalidValuesException( + "The input parameter contains an invalid url."), out); + else if (ret == DOWNLOAD_ERROR_INVALID_DESTINATION) + ReportError(InvalidValuesException( + "The input parameter contains an invalid destination."), out); + else if (ret == DOWNLOAD_ERROR_ID_NOT_FOUND) + ReportError(InvalidValuesException("No such a download ID found"), out); + else if (ret == DOWNLOAD_ERROR_QUEUE_FULL) + ReportError(UnknownException("Download server queue is full"), out); + else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) + ReportError(SecurityException( + "The application does not have the privilege to call this method."), out); + else + ReportError(UnknownException("Unknown Error"), out); +} + +void DownloadInstance::DownloadManagerCancel + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "downloadId", out) + int downloadId, ret; + + int callbackId = static_cast(args.get("downloadId").get()); + + if (!GetDownloadID(callbackId, downloadId)) { + ReportError(NotFoundException + ("The identifier does not match any download operation in progress"), + out); + return; + } + + LoggerD("Download cancel for download ID: %d", downloadId); + + ret = download_cancel(downloadId); + + if (ret == DOWNLOAD_ERROR_NONE) + ReportSuccess(out); + else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) + ReportError(InvalidValuesException + ("The input parameter contains an invalid value."), out); + else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) + ReportError(UnknownException("Out of memory"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_STATE) + ReportError(InvalidValuesException("Invalid state"), out); + else if (ret == DOWNLOAD_ERROR_IO_ERROR) + ReportError(UnknownException("Internal I/O error"), out); + else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) + ReportError(UnknownException("Permission denied"), out); + else + ReportError(UnknownException("Unknown Error"), out); +} + +void DownloadInstance::DownloadManagerPause + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "downloadId", out) + int downloadId, ret; + + int callbackId = static_cast(args.get("downloadId").get()); + + if (!GetDownloadID(callbackId, downloadId)) { + ReportError(NotFoundException( + "The identifier does not match any download operation in progress"), + out); + return; + } + + LoggerD("Download pause for download ID: %d", downloadId); + + ret = download_pause(downloadId); + + if (ret == DOWNLOAD_ERROR_NONE) + ReportSuccess(out); + else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) + ReportError(InvalidValuesException( + "The input parameter contains an invalid value."), out); + else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) + ReportError(UnknownException("Out of memory"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_STATE) + ReportError(InvalidValuesException("Invalid state"), out); + else if (ret == DOWNLOAD_ERROR_IO_ERROR) + ReportError(UnknownException("Internal I/O error"), out); + else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) + ReportError(UnknownException("Permission denied"), out); + else + ReportError(UnknownException("Unknown Error"), out); +} + +void DownloadInstance::DownloadManagerResume + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "downloadId", out) + int downloadId, ret; + + int callbackId = static_cast(args.get("downloadId").get()); + + if (!GetDownloadID(callbackId, downloadId)) { + ReportError(NotFoundException + ("The identifier does not match any download operation in progress"), + out); + return; + } + + LoggerD("Download resume for download ID: %d", downloadId); + + ret = download_start(downloadId); + + if (ret == DOWNLOAD_ERROR_NONE) + ReportSuccess(out); + else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) + ReportError(InvalidValuesException( + "The input parameter contains an invalid value."), out); + else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) + ReportError(UnknownException("Out of memory"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_STATE) + ReportError(InvalidValuesException("Invalid state"), out); + else if (ret == DOWNLOAD_ERROR_IO_ERROR) + ReportError(UnknownException("Internal I/O error"), out); + else if (ret == DOWNLOAD_ERROR_INVALID_URL) + ReportError(InvalidValuesException( + "The input parameter contains an invalid url."), out); + else if (ret == DOWNLOAD_ERROR_INVALID_DESTINATION) + ReportError(InvalidValuesException( + "The input parameter contains an invalid destination."), out); + else if (ret == DOWNLOAD_ERROR_ID_NOT_FOUND) + ReportError(InvalidValuesException("No such a download ID found"), out); + else if (ret == DOWNLOAD_ERROR_QUEUE_FULL) + ReportError(UnknownException("Download server queue is full"), out); + else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) + ReportError(SecurityException( + "Application does not have the privilege to call this method."), out); + else + ReportError(UnknownException("Unknown Error"), out); +} +void DownloadInstance::DownloadManagerGetstate + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "downloadId", out) + int downloadId, ret; + std::string stateValue; + download_state_e state; + + int callbackId = static_cast(args.get("downloadId").get()); + + if (!GetDownloadID(callbackId, downloadId)) { + ReportError(NotFoundException + ("The identifier does not match any download operation in progress"), + out); + return; + } + + ret = download_get_state(downloadId, &state); + + if (ret == DOWNLOAD_ERROR_NONE) { + switch (state) { + case DOWNLOAD_STATE_QUEUED: + stateValue = "QUEUED"; + break; + case DOWNLOAD_STATE_DOWNLOADING: + stateValue = "DOWNLOADING"; + break; + case DOWNLOAD_STATE_PAUSED: + stateValue = "PAUSED"; + break; + case DOWNLOAD_STATE_COMPLETED: + stateValue = "COMPLETED"; + break; + case DOWNLOAD_STATE_FAILED: + stateValue = "FAILED"; + break; + case DOWNLOAD_STATE_CANCELED: + stateValue = "CANCELED"; + break; + } + + ReportSuccess(picojson::value(stateValue), out); + } else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) { + ReportError(InvalidValuesException( + "The input parameter contains an invalid value."), out); + } else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) { + ReportError(UnknownException("Out of memory"), out); + } else if (ret == DOWNLOAD_ERROR_INVALID_STATE) { + ReportError(InvalidValuesException("Invalid state"), out); + } else if (ret == DOWNLOAD_ERROR_IO_ERROR) { + ReportError(UnknownException("Internal I/O error"), out); + } else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) { + ReportError(UnknownException("Permission denied"), out); + } else { + ReportError(UnknownException("Unknown Error"), out); + } +} + +void DownloadInstance::DownloadManagerGetmimetype + (const picojson::value& args, picojson::object& out) { + CHECK_EXIST(args, "downloadId", out) + + int downloadId, ret; + char* mimetype = NULL; + + int callbackId = static_cast(args.get("downloadId").get()); + + if (!GetDownloadID(callbackId, downloadId)) { + ReportError(NotFoundException + ("The identifier does not match any download operation in progress"), + out); + return; + } + + ret = download_get_mime_type(downloadId, &mimetype); + + if (ret == DOWNLOAD_ERROR_NONE) { + LoggerD("MIMEtype for callbackID %d : %s", callbackId, mimetype); + ReportSuccess(picojson::value(mimetype), out); + } else if (ret == DOWNLOAD_ERROR_INVALID_PARAMETER) { + ReportError(InvalidValuesException( + "The input parameter contains an invalid value."), out); + } else if (ret == DOWNLOAD_ERROR_OUT_OF_MEMORY) { + ReportError(UnknownException("Out of memory"), out); + } else if (ret == DOWNLOAD_ERROR_INVALID_STATE) { + ReportError(InvalidValuesException("Invalid state"), out); + } else if (ret == DOWNLOAD_ERROR_IO_ERROR) { + ReportError(UnknownException("Internal I/O error"), out); + } else if (ret == DOWNLOAD_ERROR_PERMISSION_DENIED) { + ReportError(UnknownException("Permission denied"), out); + } else { + ReportError(UnknownException("Unknown Error"), out); + } +} + +bool DownloadInstance::GetDownloadID + (const int callback_id, int& download_id) { + + if (diMap.find(callback_id) != diMap.end()) { + download_id = diMap.find(callback_id)->second->download_id; + } else { + return false; + } + return true; +} + +void DownloadInstance::DownloadManagerGetdownloadrequest + (const picojson::value& args, picojson::object& out) { + // Nothing to do +} + +void DownloadInstance::DownloadManagerSetlistener + (const picojson::value& args, picojson::object& out) { + // Nothing to do +} + + +#undef CHECK_EXIST + +} // namespace download +} // namespace extension diff --git a/src/download/download_instance.h b/src/download/download_instance.h new file mode 100644 index 00000000..a826249a --- /dev/null +++ b/src/download/download_instance.h @@ -0,0 +1,98 @@ +// Copyright 2014 Samsung Electronics Co, Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DOWNLOAD_DOWNLOAD_INSTANCE_H_ +#define DOWNLOAD_DOWNLOAD_INSTANCE_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/extension.h" + +template + inline std::string to_string(const T& t) { + std::stringstream ss; + ss << t; + return ss.str(); + } + +namespace extension { +namespace download { + +class DownloadInstance : public common::ParsedInstance { + public: + DownloadInstance(); + virtual ~DownloadInstance(); + + private: + void DownloadManagerStart + (const picojson::value& args, picojson::object& out); + void DownloadManagerCancel + (const picojson::value& args, picojson::object& out); + void DownloadManagerPause + (const picojson::value& args, picojson::object& out); + void DownloadManagerResume + (const picojson::value& args, picojson::object& out); + void DownloadManagerGetstate + (const picojson::value& args, picojson::object& out); + void DownloadManagerGetdownloadrequest + (const picojson::value& args, picojson::object& out); + void DownloadManagerGetmimetype + (const picojson::value& args, picojson::object& out); + void DownloadManagerSetlistener + (const picojson::value& args, picojson::object& out); + + bool GetDownloadID(const int callback_id, int& download_id); + + static void OnStateChanged + (int download_id, download_state_e state, void* user_data); + static void progress_changed_cb + (int download_id, long long unsigned received, void* user_data); + static void OnStart(int download_id, void* user_data); + + static gboolean OnProgressChanged(void* user_data); + static gboolean OnFinished(void* user_data); + static gboolean OnPaused(void* user_data); + static gboolean OnCanceled(void* user_data); + static gboolean OnFailed(void* user_data); + + struct DownloadInfo { + int callbackId; + std::string url; + std::string destination; + std::string file_name; + std::string http_header; + download_network_type_e network_type; + + int download_id; + long long unsigned file_size; + }; + + struct DownloadCallback { + int callbackId; + int downloadId; + DownloadInstance* instance; + unsigned long long received; + download_state_e state; + }; + + typedef std::vector DownloadCallbackVector; + typedef std::shared_ptr DownloadInfoPtr; + typedef std::map DownloadInfoMap; + + DownloadCallbackVector downCbVector; + DownloadInfoMap diMap; +}; + +} // namespace download +} // namespace extension + +#endif // DOWNLOAD_DOWNLOAD_INSTANCE_H_ diff --git a/src/tizen-wrt.gyp b/src/tizen-wrt.gyp index b8511ce1..3a79c8c6 100644 --- a/src/tizen-wrt.gyp +++ b/src/tizen-wrt.gyp @@ -31,6 +31,7 @@ 'calendar/calendar.gyp:*', 'datacontrol/datacontrol.gyp:*', 'datasync/datasync.gyp:*', + 'download/download.gyp:*', 'messaging/messaging.gyp:*', 'networkbearerselection/networkbearerselection.gyp:*', 'nfc/nfc.gyp:*',