From: WonYoung Choi Date: Thu, 12 Nov 2015 02:04:13 +0000 (+0900) Subject: Introduce JSNative project X-Git-Tag: submit/tizen/20151218.005349~19 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0075a121376df6d73a220a859e552e541f1cc136;p=platform%2Fframework%2Fnative%2Fjsnative.git Introduce JSNative project JSNative consists of set of node.js modules to provide node.js environment for Tizen platform. Currently, JSNative has below node.js modules. - appfw : Using tizen application frameworks - gcontext: Using glib mainloop - node-xwalk : Using crosswalk style extension - jsn-cli : JSNative CLI tools Change-Id: I8fcd5d8fa43150b06d3db3d53625ddfbaf3e731d --- diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9d5f315 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +PROJECT("jsnative") + +INCLUDE(FindPkgConfig) + +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Release") +ENDIF(NOT CMAKE_BUILD_TYPE) + +SET(CMAKE_C_FLAGS_PROFILING "-O2") +SET(CMAKE_CXX_FLAGS_PROFILING "-O2 -std=c++0x") +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -std=c++0x -g") +SET(CMAKE_C_FLAGS_RELEASE "-O2 -g") +SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -std=c++0x -g -fvisibility-inlines-hidden") +SET(CMAKE_CXX_FLAGS_CCOV "-O0 -std=c++0x -g --coverage") + +ADD_DEFINITIONS("-fPIC") +ADD_DEFINITIONS("-Wall") # Generate all warnings +ADD_DEFINITIONS("-Wextra") # Generate even more extra warnings +ADD_DEFINITIONS("-Wno-variadic-macros") # Inhibit variadic macros warnings (needed for ORM) +ADD_DEFINITIONS("-Wno-deprecated") # No warnings about deprecated features +ADD_DEFINITIONS("-std=c++0x") # accept C++11x standard + +SET(GLOBAL_NODE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/lib/node) + +ADD_SUBDIRECTORY(appfw) +ADD_SUBDIRECTORY(gcontext) +ADD_SUBDIRECTORY(node-xwalk) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.BSD b/LICENSE.BSD new file mode 100644 index 0000000..8c40c3e --- /dev/null +++ b/LICENSE.BSD @@ -0,0 +1,27 @@ + Copyright (c) 2015 Samsung Electronics Co, Ltd. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Samsung Electronics nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/appfw/CMakeLists.txt b/appfw/CMakeLists.txt new file mode 100755 index 0000000..965911c --- /dev/null +++ b/appfw/CMakeLists.txt @@ -0,0 +1,33 @@ +SET(TARGET_APPFW "appfw") +SET(TARGET_APPFW_NATIVE "appfw-native") +SET(TARGET_APPFW_SRC ${PROJECT_SOURCE_DIR}/appfw) + +# Copy Project +INSTALL(FILES + ${TARGET_APPFW_SRC}/package.json + DESTINATION ${GLOBAL_NODE_MODULE_PATH}/${TARGET_APPFW} +) + +INSTALL(FILES + ${TARGET_APPFW_SRC}/lib/appfw.js + DESTINATION ${GLOBAL_NODE_MODULE_PATH}/${TARGET_APPFW}/lib +) + +# Native Binding Module +PKG_CHECK_MODULES(TARGET_APPFW_NATIVE_DEPS REQUIRED + appcore-efl aul dlog nodejs +) +INCLUDE_DIRECTORIES ( + ${TARGET_APPFW_SRC}/src + ${TARGET_APPFW_NATIVE_DEPS_INCLUDE_DIRS} +) +ADD_LIBRARY(${TARGET_APPFW_NATIVE} MODULE + ${TARGET_APPFW_SRC}/src/appfw.cc + ${TARGET_APPFW_SRC}/src/appfw_native_node.cc +) +SET_TARGET_PROPERTIES(${TARGET_APPFW_NATIVE} PROPERTIES PREFIX "" SUFFIX ".node") +TARGET_LINK_LIBRARIES(${TARGET_APPFW_NATIVE} + ${TARGET_APPFW_NATIVE_DEPS_LIBRARIES} +) +INSTALL(TARGETS ${TARGET_APPFW_NATIVE} DESTINATION + ${GLOBAL_NODE_MODULE_PATH}/${TARGET_APPFW}/build/Release) diff --git a/appfw/lib/appfw.js b/appfw/lib/appfw.js new file mode 100755 index 0000000..c41ba63 --- /dev/null +++ b/appfw/lib/appfw.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var util = require('util'); +var EventEmitter = require('events'); + +var map = new WeakMap(); +var internal = function (object) { + if (!map.has(object)) + map.set(object, {}); + return map.get(object); +} + +var Application = function() { + EventEmitter.call(this); + internal(this).initialized = false; +}; + +util.inherits(Application, EventEmitter); + +Application.prototype.init = function(name) { + if (internal(this).initialized) + return; + internal(this).initialized = true; + process.title = process.argv[1]; + + var appfw = require('../build/Release/appfw-native.node'); + var EventEmitter = require('events'); + + // gcontext module should be installed as global module + require('gcontext').init(); + + process.on('exit', function(){ + appfw.deinit(); + }); + + appfw.onCreate = (function(self){ + return self.emit.bind(self, ['create']); + })(this); + + appfw.onService = (function(self){ + return self.emit.bind(self, ['service']); + })(this); + + appfw.onPause = (function(self){ + return self.emit.bind(self, ['pause']); + })(this); + + appfw.onResume = (function(self){ + return self.emit.bind(self, ['resume']); + })(this); + + appfw.onTerminate = (function(self){ + return self.emit.bind(self, ['terminate']); + })(this); + + appfw.init(process.argv.slice(1)); +} + +Application.prototype.exit = function(code) { + process.exit(code); +} + +module.exports = new Application(); diff --git a/appfw/package.json b/appfw/package.json new file mode 100644 index 0000000..7816c01 --- /dev/null +++ b/appfw/package.json @@ -0,0 +1,10 @@ +{ + "name": "appfw", + "version": "0.0.1", + "description": "Application framework module for Tizen", + "main": "./lib/appfw.js", + "author": { + "name": "Seungkeun Lee", + "email": "sngn.lee@samsung.com" + } +} diff --git a/appfw/src/appfw.cc b/appfw/src/appfw.cc new file mode 100755 index 0000000..81996fd --- /dev/null +++ b/appfw/src/appfw.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "appfw.h" + +#include +#include +#include +#include +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "JS_BINDING" + +namespace plugins { +AppFW* AppFW::GetInstance() { + static AppFW instance; + return &instance; +} + +AppFW::AppFW() : initialized_(false), + create_handler_(nullptr), + service_handler_(nullptr), + terminate_handler_(nullptr), + pause_handler_(nullptr), + resume_handler_(nullptr) { +} + +AppFW::~AppFW() { +} + +bool AppFW::Init(int argc, char* argv[]) { + if (initialized_) + return false; + + initialized_ = true; + static struct appcore_ops ops; + + ops.create = [](void* data) { + AppFW* appfw = static_cast(data); + if (appfw->create_handler_) + appfw->create_handler_(); + return 0; + }; + + ops.terminate = [](void* data) { + AppFW* appfw = static_cast(data); + if (appfw->terminate_handler_) + appfw->terminate_handler_(); + return 0; + }; + + ops.pause = [](void* data) { + AppFW* appfw = static_cast(data); + if (appfw->pause_handler_) + appfw->pause_handler_(); + return 0; + }; + + ops.resume = [](void* data) { + AppFW* appfw = static_cast(data); + if (appfw->resume_handler_) + appfw->resume_handler_(); + return 0; + }; + + ops.reset = [](bundle* b, void* data) { + AppFW* appfw = static_cast(data); + if (appfw->service_handler_) + appfw->service_handler_(b); + return 0; + }; + + ops.data = this; + + // TODO(sngn.lee) : Delete me, Temporary code to hack app terminate + auto status_cb = [](int status, void* data){ + AppFW* appfw = static_cast(data); + if (status == STATUS_DYING) { + uv_stop(uv_default_loop()); + appfw->Deinit(); + } + return 0; + }; + aul_add_status_local_cb(status_cb, this); + // end of TODO + + appcore_efl_init("js-binding", &argc, &argv, &ops); + + return true; +} + +void AppFW::Deinit() { + LOGE("AppFW Deinit was called.."); + if (initialized_) { + initialized_ = false; + appcore_efl_fini(); + } +} + +void AppFW::set_create_handler(std::function handler) { + create_handler_ = handler; +} +void AppFW::set_service_handler(std::function handler) { + service_handler_ = handler; +} +void AppFW::set_terminate_handler(std::function handler) { + terminate_handler_ = handler; +} +void AppFW::set_pause_handler(std::function handler) { + pause_handler_ = handler; +} +void AppFW::set_resume_handler(std::function handler) { + resume_handler_ = handler; +} + +} // namespace plugins diff --git a/appfw/src/appfw.h b/appfw/src/appfw.h new file mode 100755 index 0000000..d6a1d98 --- /dev/null +++ b/appfw/src/appfw.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace plugins { + +class AppFW { + public: + static AppFW* GetInstance(); + bool Init(int argc, char* argv[]); + void Deinit(); + void set_create_handler(std::function handler); + void set_service_handler(std::function handler); + void set_terminate_handler(std::function handler); + void set_pause_handler(std::function handler); + void set_resume_handler(std::function handler); + + // TODO(sngn.lee) : Add system event callback. eg) low battery, low memory... + + private: + AppFW(); + ~AppFW(); + bool initialized_; + std::function create_handler_; + std::function service_handler_; + std::function terminate_handler_; + std::function pause_handler_; + std::function resume_handler_; +}; + +} // namespace plugins diff --git a/appfw/src/appfw_native_node.cc b/appfw/src/appfw_native_node.cc new file mode 100755 index 0000000..27bfa59 --- /dev/null +++ b/appfw/src/appfw_native_node.cc @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include + +#include + + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "JS_BINDING" + +namespace { + +v8::Persistent gThis_; +const char* kCreateHandlerKey = "onCreate"; +const char* kTerminateHandlerKey = "onTerminate"; +const char* kServiceHandlerKey = "onService"; +const char* kPauseHandlerKey = "onPause"; +const char* kResumeHandlerKey = "onResume"; + +v8::Handle GetHandler(const std::string& name) { + if (gThis_.IsEmpty()) { + LOGD("this object is undefined"); + return v8::Handle(); + } + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::EscapableHandleScope handle_scope(isolate); + + v8::Handle self = v8::Local::New(isolate, gThis_); + v8::Local handler_value = + self->Get(v8::String::NewFromUtf8(isolate, name.c_str())); + + if (!handler_value->IsFunction()) { + LOGE("%s handler is undefined", name.c_str()); + return v8::Handle(); + } + return handle_scope.Escape(v8::Handle::Cast(handler_value)); +} + +void SetCreateHandler() { + plugins::AppFW* appfw = plugins::AppFW::GetInstance(); + appfw->set_create_handler([](){ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Handle handler_function = GetHandler(kCreateHandlerKey); + if (handler_function.IsEmpty()) { + return; + } + v8::TryCatch try_catch; + handler_function->Call(v8::Local::New(isolate, gThis_), + 0, nullptr); + if (try_catch.HasCaught()) + LOGE("Exception when running create handler"); + }); +} + +void SetTerminateHandler() { + plugins::AppFW* appfw = plugins::AppFW::GetInstance(); + appfw->set_terminate_handler([](){ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Handle handler_function = + GetHandler(kTerminateHandlerKey); + if (handler_function.IsEmpty()) { + return; + } + v8::TryCatch try_catch; + handler_function->Call(v8::Local::New(isolate, gThis_), + 0, nullptr); + if (try_catch.HasCaught()) + LOGE("Exception when running terminate handler"); + }); +} + +void SetPauseHandler() { + plugins::AppFW* appfw = plugins::AppFW::GetInstance(); + appfw->set_pause_handler([](){ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Handle handler_function = + GetHandler(kPauseHandlerKey); + if (handler_function.IsEmpty()) { + return; + } + v8::TryCatch try_catch; + handler_function->Call(v8::Local::New(isolate, gThis_), + 0, nullptr); + if (try_catch.HasCaught()) + LOGE("Exception when running pause handler"); + }); +} + +void SetResumeHandler() { + plugins::AppFW* appfw = plugins::AppFW::GetInstance(); + appfw->set_resume_handler([](){ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Handle handler_function = + GetHandler(kResumeHandlerKey); + if (handler_function.IsEmpty()) { + return; + } + v8::TryCatch try_catch; + handler_function->Call(v8::Local::New(isolate, gThis_), + 0, nullptr); + if (try_catch.HasCaught()) + LOGE("Exception when running resume handler"); + }); +} + +void SetServiceHandler() { + plugins::AppFW* appfw = plugins::AppFW::GetInstance(); + appfw->set_service_handler([](void* bundle){ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope handle_scope(isolate); + v8::Handle handler_function = + GetHandler(kServiceHandlerKey); + if (handler_function.IsEmpty()) { + return; + } + v8::TryCatch try_catch; + handler_function->Call(v8::Local::New(isolate, gThis_), + 0, nullptr); + if (try_catch.HasCaught()) + LOGE("Exception when running service handler"); + }); +} + +} // namespace + +static void AppFWInit(const v8::FunctionCallbackInfo& args) { + if (args.Length() < 1 || !args[0]->IsArray()) + return; + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + + gThis_.Reset(v8::Isolate::GetCurrent(), args.This()); + + SetCreateHandler(); + SetTerminateHandler(); + SetPauseHandler(); + SetResumeHandler(); + SetServiceHandler(); + + + v8::Local array_args = v8::Local::Cast(args[0]); + int argc = array_args->Length(); + std::vector argv(argc); + for (int i = 0; i < argc; i++) { + v8::Local arg = array_args->Get(i)->ToString(); + int nsize = arg->Utf8Length()+1; + argv[i] = new char[nsize]; + arg->WriteUtf8(argv[i]); + } + plugins::AppFW::GetInstance()->Init(argc, argv.data()); + for (int i = 0; i < argc; i++) { + delete[] argv[i]; + } +} + +static void AppFWDeinit(const v8::FunctionCallbackInfo& args) { + plugins::AppFW::GetInstance()->Deinit(); +} + +static void init(v8::Handle target) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + target->Set(v8::String::NewFromUtf8(isolate, "init"), + v8::FunctionTemplate::New(isolate, AppFWInit)->GetFunction()); + target->Set(v8::String::NewFromUtf8(isolate, "deinit"), + v8::FunctionTemplate::New(isolate, AppFWDeinit)->GetFunction()); +} + +NODE_MODULE(appfw_native, init); diff --git a/gcontext/CMakeLists.txt b/gcontext/CMakeLists.txt new file mode 100755 index 0000000..ac9003a --- /dev/null +++ b/gcontext/CMakeLists.txt @@ -0,0 +1,28 @@ +SET(TARGET_GCONTEXT "gcontext") +SET(TARGET_GCONTEXT_NATIVE "gcontext") +SET(TARGET_GCONTEXT_SRC ${PROJECT_SOURCE_DIR}/gcontext) + +# Copy Project +INSTALL(FILES + ${TARGET_GCONTEXT_SRC}/package.json + DESTINATION ${GLOBAL_NODE_MODULE_PATH}/${TARGET_GCONTEXT} +) + + +# Native Binding Module +PKG_CHECK_MODULES(TARGET_GCONTEXT_NATIVE_DEPS REQUIRED + dlog glib-2.0 gobject-2.0 nodejs +) +INCLUDE_DIRECTORIES ( + ${TARGET_GCONTEXT_SRC}/src + ${TARGET_GCONTEXT_NATIVE_DEPS_INCLUDE_DIRS} +) +ADD_LIBRARY(${TARGET_GCONTEXT_NATIVE} MODULE + ${TARGET_GCONTEXT_SRC}/src/gcontext.cc +) +SET_TARGET_PROPERTIES(${TARGET_GCONTEXT_NATIVE} PROPERTIES PREFIX "" SUFFIX ".node") +TARGET_LINK_LIBRARIES(${TARGET_GCONTEXT_NATIVE} + ${TARGET_GCONTEXT_NATIVE_DEPS_LIBRARIES} +) +INSTALL(TARGETS ${TARGET_GCONTEXT_NATIVE} DESTINATION + ${GLOBAL_NODE_MODULE_PATH}/${TARGET_GCONTEXT}/build/Release) diff --git a/gcontext/package.json b/gcontext/package.json new file mode 100644 index 0000000..1102058 --- /dev/null +++ b/gcontext/package.json @@ -0,0 +1,10 @@ +{ + "name": "gcontext", + "version": "0.0.1", + "description": "Utility module for integrating glib mainloop", + "main": "./build/Release/gcontext", + "author": { + "name": "Seungkeun Lee", + "email": "sngn.lee@samsung.com" + } +} diff --git a/gcontext/src/gcontext.cc b/gcontext/src/gcontext.cc new file mode 100755 index 0000000..3cbdb95 --- /dev/null +++ b/gcontext/src/gcontext.cc @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gcontext.h" + +#include +#include +#include +#include +#include +#include + +#include + + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "JS_BINDING" + +namespace wrt { +namespace service { + +struct poll_handler { + int fd; + uv_poll_t *uv_poll; + int eventmask; + int ref; + std::list fd_list; +}; + +GContext::GContext():initialized_(false) { +} + +GContext::~GContext() { +} + +void GContext::Init() { + if (initialized_) + return; + initialized_ = true; + GMainContext *gc = g_main_context_default(); + +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif + + g_main_context_acquire(gc); + context_ = g_main_context_ref(gc); + fd_list_ = NULL; + fd_list_size_ = 0; + fd_count_ = 0; + + // allocate memory to memory + prepare_handle_ = static_cast(malloc(sizeof(uv_prepare_t))); + check_handle_ = static_cast(malloc(sizeof(uv_check_t))); + timeout_handle_ = static_cast(malloc(sizeof(uv_timer_t))); + + memset(prepare_handle_, 0, sizeof(uv_prepare_t)); + memset(check_handle_, 0, sizeof(uv_check_t)); + memset(timeout_handle_, 0, sizeof(uv_timer_t)); + + uv_prepare_init(uv_default_loop(), prepare_handle_); + uv_check_init(uv_default_loop(), check_handle_); + uv_timer_init(uv_default_loop(), timeout_handle_); + + prepare_handle_->data = this; + check_handle_->data = this; + timeout_handle_->data = this; + + uv_prepare_start(prepare_handle_, GContext::OnPrepare); + uv_check_start(check_handle_, GContext::OnCheck); +} + +void GContext::Uninit() { + if (!initialized_) + return; + + initialized_ = false; + + /* Remove all handlers */ + std::list::iterator itr = poll_handle_list_.begin(); + while (itr != poll_handle_list_.end()) { + /* Stop polling handler */ + uv_unref(reinterpret_cast((*itr)->uv_poll)); + uv_poll_stop((*itr)->uv_poll); + uv_close(reinterpret_cast((*itr)->uv_poll), + reinterpret_cast(::free)); + delete *itr; + itr = poll_handle_list_.erase(itr); + } + + uv_unref(reinterpret_cast(check_handle_)); + uv_unref(reinterpret_cast(prepare_handle_)); + uv_unref(reinterpret_cast(timeout_handle_)); + + uv_check_stop(check_handle_); + uv_prepare_stop(prepare_handle_); + uv_timer_stop(timeout_handle_); + + uv_close(reinterpret_cast(check_handle_), + reinterpret_cast(::free)); + uv_close(reinterpret_cast(prepare_handle_), + reinterpret_cast(::free)); + uv_close(reinterpret_cast(timeout_handle_), + reinterpret_cast(::free)); + + check_handle_ = NULL; + prepare_handle_ = NULL; + timeout_handle_ = NULL; + + g_free(fd_list_); + fd_list_ = NULL; + + /* Release GMainContext loop */ + g_main_context_unref(context_); +} + +static void poll_cb(uv_poll_t *uv_handle, int status, int events) { + poll_handler* handle = static_cast(uv_handle->data); + if (status == 0) { + std::list::iterator itr = handle->fd_list.begin(); + while (itr != handle->fd_list.end()) { + GPollFD* pfd = *itr; + pfd->revents |= pfd->events & ((events & UV_READABLE ? G_IO_IN : 0) + | (events & UV_WRITABLE ? G_IO_OUT : 0)); + ++itr; + } + } else { + uv_poll_stop(uv_handle); + } +} + +void GContext::onPrepare() { + int i; + int timeout; + + g_main_context_prepare(context_, &max_priority_); + + // Getting all sources from GLib main context + while (fd_list_size_ < (fd_count_ = g_main_context_query( + context_, + max_priority_, + &timeout, + fd_list_, + fd_list_size_))) { + g_free(fd_list_); + fd_list_size_ = fd_count_; + fd_list_ = g_new(GPollFD, fd_list_size_); + } + + // Poll + if (fd_count_ > 0) { + std::unique_ptr flagsTable(new char[fd_count_]); + memset(flagsTable.get(), 0, fd_count_); + std::list::iterator itr = poll_handle_list_.begin(); + + // for each poll handler + while (itr != poll_handle_list_.end()) { + poll_handler* handle = *itr; + int origin_mask = handle->eventmask; + handle->ref = 0; + handle->eventmask = 0; + handle->fd_list.clear(); + + // check already initialized poll handles + for (i = 0; i < fd_count_; ++i) { + GPollFD *pfd = fd_list_ + i; + if (handle->fd == pfd->fd) { + flagsTable.get()[i] = 1; + handle->ref++; + pfd->revents = 0; + handle->eventmask |= (pfd->events & G_IO_IN ? UV_READABLE: 0) + | (pfd->events & G_IO_OUT ? UV_WRITABLE: 0); + handle->fd_list.push_back(pfd); + } + } + + // remasking events + if (handle->ref > 0) { + if (handle->eventmask != origin_mask) { + uv_poll_stop(handle->uv_poll); + uv_poll_start(handle->uv_poll, handle->eventmask, poll_cb); + } + } + + // remove unused poll handles + if (handle->ref == 0) { + uv_unref(reinterpret_cast(handle->uv_poll)); + uv_poll_stop(handle->uv_poll); + uv_close(reinterpret_cast(handle->uv_poll), + reinterpret_cast(::free)); + itr = poll_handle_list_.erase(itr); + delete handle; + } else { + ++itr; + } + } + + std::list new_poll_fds; + /* Process current file descriptors from GContext */ + for (i = 0; i < fd_count_; ++i) { + GPollFD *pfd = &fd_list_[i]; + int exists = flagsTable.get()[i]; + if (exists) + continue; + + pfd->revents = 0; + for (itr = new_poll_fds.begin(); itr != new_poll_fds.end(); ++itr) { + poll_handler *handle = *itr; + if (handle->fd == pfd->fd) { + int oldmask = handle->eventmask; + handle->eventmask |= (pfd->events & G_IO_IN ? UV_READABLE: 0) + | (pfd->events & G_IO_OUT ? UV_WRITABLE: 0); + if (oldmask != handle->eventmask) { + uv_poll_stop(handle->uv_poll); + uv_poll_start(handle->uv_poll, handle->eventmask, poll_cb); + } + exists = 1; + break; + } + } + + if (exists) + continue; + + /* Preparing poll handler */ + struct poll_handler* handle = new poll_handler(); + handle->fd = pfd->fd; + handle->ref = 1; + + /* Create uv poll handler, then append own poll handler on it */ + uv_poll_t *pt = static_cast(malloc(sizeof(uv_poll_t))); + memset(pt, 0, sizeof(uv_poll_t)); + pt->data = handle; + handle->uv_poll = pt; + + uv_poll_init(uv_default_loop(), pt, pfd->fd); + int uv_events = (pfd->events & G_IO_IN ? UV_READABLE: 0) + | (pfd->events & G_IO_OUT ? UV_WRITABLE: 0); + handle->eventmask = uv_events; + uv_poll_start(pt, uv_events, poll_cb); + new_poll_fds.push_back(handle); + } + if (!new_poll_fds.empty()) + poll_handle_list_.merge(new_poll_fds); + } + + if (timeout >= 0) { + uv_timer_start(timeout_handle_, OnTimeout, timeout, 0); + } +} + + +void GContext::OnPrepare(uv_prepare_t* handle) { + GContext *This = static_cast(handle->data); + This->onPrepare(); +} + +void GContext::OnCheck(uv_check_s* handle) { + GContext* This = static_cast(handle->data); + This->onCheck(); +} + +void GContext::onCheck() { + int ready = g_main_context_check(context_, + max_priority_, + fd_list_, + fd_count_); + if (ready) { + g_main_context_dispatch(context_); + } +} + +void GContext::OnTimeout(uv_timer_s* handle) { + GContext* This = static_cast(handle->data); + This->onTimeout(); +} + +void GContext::onTimeout() { +} + + +static GContext *g_context = NULL; + +static void GContextInit(const v8::FunctionCallbackInfo&) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + + if (g_context == NULL) { + g_context = new GContext(); + } + g_context->Init(); +} + +static void GContextUninit(const v8::FunctionCallbackInfo&) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + if (g_context != NULL) { + g_context->Uninit(); + delete g_context; + g_context = NULL; + } +} + +static void init(v8::Handle target) { + NODE_SET_METHOD(target, "init", GContextInit); + NODE_SET_METHOD(target, "uninit", GContextUninit); +} + +NODE_MODULE(gcontext, init); + +} // namespace service +} // namespace wrt diff --git a/gcontext/src/gcontext.h b/gcontext/src/gcontext.h new file mode 100755 index 0000000..261ebf5 --- /dev/null +++ b/gcontext/src/gcontext.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WRT_SERVICE_NODE_GCONTEXT_H_ +#define WRT_SERVICE_NODE_GCONTEXT_H_ + +#include +#include + +#include + +namespace wrt { +namespace service { + +class poll_handler; + +class GContext { + public: + GContext(); + virtual ~GContext(); + void Init(); + void Uninit(); + + private: + void onPrepare(); + void onCheck(); + void onTimeout(); + + static void OnPrepare(uv_prepare_t* handle); + static void OnCheck(uv_check_s* handle); + static void OnTimeout(uv_timer_s* handle); + + bool initialized_; + GMainContext *context_; + int max_priority_; + + GPollFD *fd_list_; + int fd_list_size_; + int fd_count_; + std::list poll_handle_list_; + + uv_prepare_t *prepare_handle_; + uv_check_t *check_handle_; + uv_timer_t *timeout_handle_; +}; + +} // namespace service +} // namespace wrt + +#endif // WRT_SERVICE_NODE_GCONTEXT_H_ diff --git a/jsn-cli/bin/jsn-cli b/jsn-cli/bin/jsn-cli new file mode 100755 index 0000000..6e383b5 --- /dev/null +++ b/jsn-cli/bin/jsn-cli @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +const cli = require('../lib/cli'); +cli(process.argv); diff --git a/jsn-cli/lib/build.js b/jsn-cli/lib/build.js new file mode 100755 index 0000000..c99403b --- /dev/null +++ b/jsn-cli/lib/build.js @@ -0,0 +1,71 @@ +'use strict' + +const fs = require('fs'); +const path = require('path'); +const Q = require('q'); + +const JsnError = require('./jsn_error'); +const shellUtil = require('./shell_util'); + +module.exports = build; + +// this module's build is meanging to package a tpk +function build(argv, parser) { + const needToSign = argv.args_[0] || 'nosign'; + switch(needToSign) { + case 'sign': + console.log('Build command will package with signing'); + + // current args_[0] is 'sign'. So remove it. + argv.args_.shift(); + return require('./sign')(argv, parser) + .then(() => packageToTpk()); + + case 'nosign': + console.log('Build command will package without signing'); + return parser.parse() + .catch(err => { + throw new JsnError('Parsing manifest file has a problem: ' + + err.message); + }) + .then(() => packageToTpk()); + + default: + throw new JsnError('Wrong parameters for build command: ' + + needToSign); + } + + function packageToTpk() { + const dotTpk = '.tpk'; + const dirToGoBack = process.cwd(); + + console.log('Building the package'); + + // exist 'zip' command? + return shellUtil.shExec('which zip') + + .then(() => { + // cd to project directory + process.chdir(parser.get('exec_path')); + + const pkgIdTpk = parser.get('package').pkg_id + dotTpk; + + // if *.tpk file exists, delete it + return Q.denodeify(fs.stat)(pkgIdTpk) + .then(stats => shellUtil.shExec('rm ' + pkgIdTpk), + () => { + // empty(if not, it is catrued by outer catch ) + }); + }) + + .then(() => { + const tpkFile = parser.get('package').pkg_id + dotTpk; + const output = '.' + path.sep + tpkFile; + const input = '.' + path.sep + '*'; + return shellUtil.shExec('zip -r ' + output + ' ' + input); + }) + + // cd to previous cwd; actually, doesn't need this + .then(() => process.chdir(dirToGoBack)); + } +} diff --git a/jsn-cli/lib/cli.js b/jsn-cli/lib/cli.js new file mode 100755 index 0000000..93196e1 --- /dev/null +++ b/jsn-cli/lib/cli.js @@ -0,0 +1,130 @@ +'use strict' + +const fs = require('fs'); +const path = require('path'); +const Q = require('q'); + +const help = require('./help'); + +module.exports = cli; + +/** + * This program uses 'Promises'. + * Reference below links for detailed contents. + * Promises: http://www.html5rocks.com/en/tutorials/es6/promises/ + * q: https://github.com/kriskowal/q + */ +function cli(inputArgv) { + // verify inputArgv + const argv = getVerifiedArgv(inputArgv); + if (argv === null) { + help.printAll(); + return; + } + + // add exec path + const execPath = process.cwd(); + argv.exec_path_ = execPath; + + // setup manifest parser + const parser = new (require('./parser'))(execPath); + + // get real path because it can be real bin file or symbolic link file + const binDir = path.dirname(fs.realpathSync(process.argv[1])); + + // change cwd + process.chdir(path.join(binDir, '..', 'lib')); + + // process command + switch (argv.cmd_) { + case 'create': + return require('./create')(argv, parser) + .then( + () => console.log('Ok'), + (err) => { + console.error(err.message); + help.printCreate(); + }); + + case 'list': + return require('./list')(argv, parser) + .then( + () => console.log('Ok'), + (err) => { + console.error(err.message); + help.printList(); + }); + + case 'remove': + return require('./remove')(argv, parser) + .then( + () => console.log('Ok'), + (err) => { + console.error(err.message); + help.printRemove(); + }); + + case 'sign': + return require('./sign')(argv, parser) + .then( + () => console.log('Ok'), + (err) => { + console.error(err.message); + help.printSign(); + }); + + case 'build': + return require('./build')(argv, parser) + .then( + () => console.log('Ok'), + (err) => { + console.error(err.message); + help.printBuild(); + }); + + case 'help': + default: + help.print(argv.args_[0]); + break; + } + + function getVerifiedArgv(inputArgv) { + if (inputArgv.length < 3) + return null; + + const cmd = inputArgv[2]; + const args = []; + switch (cmd) { + case 'create': + args.push(inputArgv[3]); + args.push(inputArgv[4]); + break; + + case 'remove': + args.push(inputArgv[3]); + break; + + case 'sign': + case 'build': + const len = inputArgv.length; + for (let i = 3; i < len; i+=1) + args[i-3] = inputArgv[i]; + break; + + case 'list': + break; + + case 'help': + args.push(inputArgv[3]); + break; + + default: + return null; + } + + return { + cmd_: cmd, + args_: args + }; + } +} diff --git a/jsn-cli/lib/create.js b/jsn-cli/lib/create.js new file mode 100755 index 0000000..bfb502f --- /dev/null +++ b/jsn-cli/lib/create.js @@ -0,0 +1,237 @@ +'use strict' + +const fs = require('fs'); +const path = require('path'); +const Q = require('q'); +const format = require('string-template'); + +const JsnError = require('./jsn_error'); +const shellUtil = require('./shell_util'); + +module.exports = create; + +function create(argv, parser) { + console.log(''); + + const manifestFile = 'tizen-manifest.xml'; + const iconFile = 'icon.png'; + + return Q.fcall(() => { + if (argv.args_[0] === undefined || argv.args_[1] === undefined) { + throw new JsnError('You should type proper parameters'); + } else { + const createType = argv.args_[0]; + console.log('Create Type: %s', createType); + + switch (createType) { + case 'package': + const pkgId = argv.args_[1]; + return createPackage(pkgId, parser); + + case 'app': + const appId = argv.args_[1]; + return createApp(appId, parser); + + default: + throw new JsnError('You should type proper parameters'); + } + } + }) + + /** + * creates directory to be below directory structure + * [pkgid]/shared(dir) + * /res(dir) + * /bin(dir) + * /lib(dir) + * /res(dir) + * /icon.png(file) + * /tizen-manifest.xml(file) + */ + function createPackage(pkgId, parser) { + console.log('Creating Package'); + console.log(parser.get('exec_path')); + const dirToCreate = path.join(parser.get('exec_path'), pkgId); + + console.log('Checking directory to create package'); + + /** + * if the dir exists and the dir is not empty, exit program + * if the dir exists and the dir is empty, pass + * if the dir exists and the dir is file, exit program + * if there is no dir, create the dir + */ + return Q.denodeify(fs.stat)(dirToCreate) + .then( + stats => { + if (stats.isDirectory()) { + return Q.denodeify(fs.readdir)(dirToCreate) + .then(files => { + if (files.length > 0) + throw new JsnError(pkgId + ' is not empty directory'); + }); + } else { + throw new JsnError(pkgId + ' file exists.'); + } + }, + () => { + console.log(dirToCreate + ' isn\'t existed'); + console.log('Generating a new package dir : %s', dirToCreate); + return Q.denodeify(fs.mkdir)(dirToCreate); + }) + + /** + * creates directories like below directory structure + * [pkgid]/shared + * /bin + * /lib + * /res + */ + .then(() => { + console.log('Creating a new jsn project package'); + console.log('Creating directories in project'); + const shExecPromises = ['shared', 'bin', 'lib', 'res'] + .map(dir => 'mkdir ' + path.join(dirToCreate, dir)) + .map(shellUtil.shExec); + return Q.all(shExecPromises) + .catch(reason => { throw new JsnError(reason); }); + }) + + /** + * creates a 'res' directory under 'shared' directory + * [pkgid]/shared + * /res + */ + .then(() => { + console.log('Creating directories in project/shared'); + return Q.denodeify(fs.mkdir)(path.join(dirToCreate, 'shared', 'res')); + }) + + /** + * copy icon from 'tizen-app-template' + * [pkgid]/res + * /icon.png + */ + .then(() => { + console.log('Copying icon'); + const defaultIconPath = path.join('..', 'tizen-app-template', iconFile); + const iconCmd = 'cp ' + defaultIconPath + ' ' + + path.join(dirToCreate, 'shared', 'res', iconFile); + return shellUtil.shExec(iconCmd) + .catch(reason => { throw new JsnError(reason); }); + }) + + /** + * creates manifest file from 'tizen-app-template' + * [pkgid]/tizen-manifest.xml + */ + .then(() => { + console.log('Creating manifest'); + const oriManifest = path.join('..', 'tizen-app-template', manifestFile); + return Q.denodeify(fs.readFile)(oriManifest); + }) + .then(data => { + const manifestContent = format(data.toString(), { pkgid: pkgId }); + const newManifest = path.join(parser.get('exec_path'), + pkgId, manifestFile); + return Q.denodeify(fs.writeFile)(newManifest, manifestContent) + .thenResolve(newManifest); + }) + + .catch(err => { + throw new JsnError('Creating package is failed: ' + err.message); + }); + } // createPackage + + /** + * creates directories to be below directory structure + * [pkgid]/shared(dir) + * /res(dir) + * /bin(dir) + * /[pkgid].[appid] + * /lib(dir) + * /res(dir) + * /[appid] + * /index.js + * /icon.png(file) + * /tizen-manifest.xml(file) - add 'ui-application' element + */ + function createApp(appId, parser) { + console.log('createApp'); + + return parser.parse() + .catch(err => { + throw new JsnError('Parsing manifest file has a problem: ' + + err.message); + }) + + .then(() => { + console.log('Creating a new appication.'); + console.log('Checking whether the app(%s) exists', appId); + const app = parser.getAppSync(appId); + if (app) { + throw new JsnError(appId + " already existed"); + } + }) + + // add 'ui-application' element in manifest file + .then(() => { + console.log('Addding element in manifest file'); + return parser.addUiAppInManifest(appId) + .catch(err => { throw err; }); + }) + + /** + * creates a bin file by copying and editing it + * [pkgid]/bin(dir) + * /[pkgid].[appid] + */ + .then(() => { + console.log('Creating bin file'); + const defaultBinPath = + path.join('..', 'tizen-app-template', 'pkgid.appid'); + return Q.denodeify(fs.readFile)(defaultBinPath) + .then(data => { + // edit format string by 'string-format' + const binContent = format(data.toString(), { appid: appId }); + const binPath = path.join(parser.get('exec_path'), 'bin', + parser.get('package').pkg_id + + '.' + appId); + return Q.denodeify(fs.writeFile)(binPath, binContent, + { mode: 0o775 }); + }); + }) + + /** + * creates directory and file + * [pkgid]/res(dir) + * /[appid] + * /index.js + */ + .then(() => { + console.log('Creating app directory'); + const appDir = path.join(parser.get('exec_path'), 'res', appId); + return Q.denodeify(fs.mkdir)(appDir); + }) + .then(() => { + console.log('Creating index.js'); + const defaultIndexPath = + path.join('..', 'tizen-app-template', 'index.js'); + return Q.denodeify(fs.readFile)(defaultIndexPath) + .then(data => { + // edit format string by 'string-format' + const indexContent = format(data.toString(), { appid: appId }); + const indexPath = + path.join(path.join(parser.get('exec_path'), 'res', appId), + 'index.js'); + return Q.denodeify(fs.writeFile)(indexPath, indexContent, + { mode: 0o775 }); + }); + }) + + .catch(err => { + throw new JsnError('Creating app(' + appId + ') is failed: ' + + err.message); + }); + } // createApp +} // create diff --git a/jsn-cli/lib/help.js b/jsn-cli/lib/help.js new file mode 100644 index 0000000..b2b9235 --- /dev/null +++ b/jsn-cli/lib/help.js @@ -0,0 +1,156 @@ +'use strict' + +const CLI = 'jsn-cli'; +const CAUTION_EXECUTED = '%s command should be executed in project root ' + + 'where has tizen-manifest.xml'; + +const help = module.exports; + +help.printAll = () => { + console.log(''); + help.printSummary(); + console.log(''); + help.printCreate(); + console.log(''); + help.printList(); + console.log(''); + help.printRemove(); + console.log(''); + help.printSign(); + console.log(''); + help.printBuild(); + console.log(''); +}; + +help.printSummary = () => { + const summary = '$ ' + CLI + ' (command) (args0) (args1) [args_n] ...'; + console.log('\n\t' + summary); + console.log(''); +}; + +help.printCreate = () => { + const create = '$ ' + CLI + ' create ("package" or "app") (id)'; + const createEx1 = 'ex) ' + CLI + ' create package pkgid'; + const createEx2 = 'ex) ' + CLI + ' create app appid'; + console.log(''); + console.log('\t' + create); + console.log('\t\t' + createEx1); + console.log('\t\t' + createEx2); + console.log('\t\t\t !!! ' + CAUTION_EXECUTED, '"app"'); + console.log(''); +}; + +help.printList = () => { + const list = '$ ' + CLI + ' list'; + console.log(''); + console.log('\t' + list); + console.log('\t\t !!! ' + CAUTION_EXECUTED, '"list"'); + console.log(''); +}; + +help.printRemove = () => { + const remove = '$ ' + CLI + ' remove (appid)'; + const removeEx = 'ex) ' + CLI + ' remove appa'; + console.log(''); + console.log('\t' + remove); + console.log('\t\t !!! ' + CAUTION_EXECUTED, '"remove"'); + console.log('\t\t' + removeEx); + console.log(''); +}; + +help.printSign = function() { + const sign = '$ ' + CLI + ' sign (signer) ' + + '(authorCAPath) (authorP12Path) (authorPwd) ' + + '(dist1P12Path) (dist1Pwd) (dist1CAPath) ' + + '[dist2P12Path, dist2Pwd, dist2CAPath, dist2RootPath]'; + const signExplain = '(argv): must assign, [argv]: can be skipped, but ' + + 'if you not skip these, all of [4 parameters] should ' + + 'not be skipped'; + + const signer = '~/tizen-sdk/tools/ide/bin/native-signing'; + const authorCAPath ='~/tizen-sdk/tools/certificate-generator/certificates/' + + 'developer/tizen-developer-ca.cer'; + const authorP12Path = '~/tizen-sdk-data/keystore/author/yons.p12'; + const authorPwd = '1234'; + const dist1P12Path = '~/tizen-sdk/tools/certificate-generator/' + + 'certificates/distributor/tizen-distributor-signer.p12'; + const dist1Pwd = 'tizenpkcs12passfordsigner'; + const dist1CAPath = '~/tizen-sdk/tools/certificate-generator/certificates/' + + 'distributor/tizen-distributor-ca.cer'; + + const signEx = 'ex) ' + CLI + ' sign ' + signer + ' ' + authorCAPath + + ' ' + authorP12Path + ' ' + authorPwd + ' ' + dist1P12Path + + ' ' + dist1Pwd + ' ' + dist1CAPath; + + console.log(''); + console.log('\t' + sign); + console.log('\t' + signExplain); + console.log('\t\t !!! ' + CAUTION_EXECUTED, '"sign"'); + console.log('\t\t' + signEx); + console.log(''); +} + +help.printBuild = () => { + const buildDefault = '$ ' + CLI + ' build'; + const buildNoSign = '$ ' + CLI + ' build nosign'; + const buildSign = '$ ' + CLI + ' build sign (signer) ' + + '(authorCAPath) (authorP12Path) (authorPwd) ' + + '(dist1P12Path) (dist1Pwd) (dist1CAPath) ' + + '[dist2P12Path, dist2Pwd, dist2CAPath, dist2RootPath]'; + const buildSignExplain = '(argv): must assign, [argv]: can be skipped, ' + + 'but if you not skip these, all of [4 parameters] ' + + 'should not be skipped'; + + const signer = '~/tizen-sdk/tools/ide/bin/native-signing'; + const authorCAPath ='~/tizen-sdk/tools/certificate-generator/certificates/' + + 'developer/tizen-developer-ca.cer'; + const authorP12Path = '~/tizen-sdk-data/keystore/author/yons.p12'; + const authorPwd = '1234'; + const dist1P12Path = '~/tizen-sdk/tools/certificate-generator/' + + 'certificates/distributor/tizen-distributor-signer.p12'; + const dist1Pwd = 'tizenpkcs12passfordsigner'; + const dist1CAPath = '~/tizen-sdk/tools/certificate-generator/certificates/' + + 'distributor/tizen-distributor-ca.cer'; + + const buildDefaultEx = 'ex) ' + CLI + ' build'; + const buildNoSignEx = 'ex) ' + CLI + ' build nosign'; + const buildSignEx = 'ex) ' + CLI + ' build sign ' + signer + ' ' + + authorCAPath + ' ' + authorP12Path + ' ' + + authorPwd + ' ' + dist1P12Path + ' ' + + dist1Pwd + ' ' + dist1CAPath; + + console.log(''); + console.log('\t' + buildDefault); + console.log('\t' + buildNoSign); + console.log('\t\t' + 'build nosign == build (no parameters)') + console.log('\t' + buildSign); + console.log('\t' + buildSignExplain); + console.log('\t\t' + CAUTION_EXECUTED, '"build"'); + console.log('\t\t' + buildDefaultEx); + console.log('\t\t' + buildNoSignEx); + console.log('\t\t' + buildSignEx); + console.log(''); +}; + +help.print = cmd => { + switch(cmd) { + case 'create': + help.printCreate(); + break; + case 'list': + help.printList(); + break; + case 'remove': + help.printRemove(); + break; + case 'sign': + help.printSign(); + break; + case 'build': + help.printBuild(); + break; + default: + help.printAll(); + break; + } +}; diff --git a/jsn-cli/lib/jsn_error.js b/jsn-cli/lib/jsn_error.js new file mode 100644 index 0000000..647f88a --- /dev/null +++ b/jsn-cli/lib/jsn_error.js @@ -0,0 +1,12 @@ +'use strict' + +class JsnError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + this.message = message; + Error.captureStackTrace(this, this.constructor.name) + } +} + +module.exports = JsnError; diff --git a/jsn-cli/lib/list.js b/jsn-cli/lib/list.js new file mode 100755 index 0000000..96d4aaa --- /dev/null +++ b/jsn-cli/lib/list.js @@ -0,0 +1,27 @@ +'use strict' + +const path = require('path') +const Q = require('q'); + +const JsnError = require('./jsn_error'); +module.exports = function(argv, parser) { + return parser.parse() + .catch(err => { + throw new JsnError('Parsing manifest file has a problem: ' + + err.message); + }) + .then(() => { + console.log('Listing apps'); + if (parser.get('parsed') && parser.get('has_app')) { + console.log('No.\tAppId\tAppType\tExec'); + const len = parser.get('apps').length; + for (let i = 0; i < len; i++) { + const app = parser.get('apps')[i]; + console.log((i+1) + '\t' + app.app_id + '\t' + app.type + + '\t' + app.exec); + } + } else { + console.log('There is no app'); + } + }); +}; diff --git a/jsn-cli/lib/parser.js b/jsn-cli/lib/parser.js new file mode 100755 index 0000000..3cfa479 --- /dev/null +++ b/jsn-cli/lib/parser.js @@ -0,0 +1,204 @@ +'use strict' + +const fs = require('fs'); +const path = require('path'); +const et = require('elementtree'); +const pd = require('pretty-data').pd; +const Q = require('q'); +const format = require('string-template'); + +const JsnError = require('./jsn_error'); + +const MANIFEST_FILE = 'tizen-manifest.xml'; + +class Parser { + // this constructor doesn't use any Promises + constructor(execPath) { + this.exec_path = execPath; + this.manifest_path = ''; + this.has_package = false; + this.package = {}; + this.has_app = false; + this.apps = []; + + // parsed_ expresses done to parse + this.parsed = false; + + // ready_ expresses ready for parsing + this.ready = (() => { + const maybeManifestPath = path.join(this.exec_path, + MANIFEST_FILE); + const existManifest = (path => { + try { + return (fs.statSync(path)); + } catch(err) { + return false; + } + })(maybeManifestPath); + if (existManifest) { + this.manifest_path = maybeManifestPath; + return true; + } else { + return false; + } + })(); + } + + set(key, val) { + this[key] = val; + } + + get(key) { + return this[key]; + } + + /** + * parses manifest file and its contents are parsed to Parser's + * members. this method returns a promise. + */ + parse() { + if (this.ready === false) { + return Q.fcall(() => { + throw new JsnError('Parser is not ready. ' + + 'Checks whether the manifest file exists'); + }); + } + + return Q.denodeify(fs.readFile)(this.manifest_path) + .then(data => { + const etree = et.parse(data.toString()); + + // parse package + const manifestElem = etree.getroot(); + if (manifestElem) { + const pack = { + pkg_id: manifestElem.get('package'), + pkg_version: manifestElem.get('version'), + api_version: manifestElem.get('api-version') + }; + this.package = pack; + this.has_package = true; + } else { + throw new JsnError('Can\'t get manifest element ' + + 'while parsing manifest file'); + } + + // parse apps + const uiAppElems = etree.findall('./ui-application'); + if (uiAppElems) { + const len = uiAppElems.length; + this.has_app = (len > 0) ? true : false; + for (let i = 0; i < len; i++) { + const uiApp = uiAppElems[i]; + const app = { + app_id: uiApp.get('appid'), + exec: uiApp.get('exec'), + type: uiApp.get('type') + }; + this.apps[i] = app; + } + } else { + throw new JsnError('Can\'t get any ui app elements ' + + 'while parsing manifest file'); + } + this.parsed = true; + }) + + .then(() => console.log('Parsing is successful')); + } // parse + + addUiAppInManifest(appId) { + return Q.denodeify(fs.readFile)(this.manifest_path) + .then(data => { + const etree = et.parse(data.toString()); + const subElement = et.SubElement; + let root = etree.getroot(); + let uiAppElem = subElement(root, 'ui-application'); + uiAppElem.set('appid', appId); + uiAppElem.set('exec', this.package.pkg_id + '.' + appId); + uiAppElem.set('type', 'jsapp'); + uiAppElem.set('multiple', 'false'); + uiAppElem.set('taskmanage', 'true'); + uiAppElem.set('nodisplay', 'false'); + + let iconElem = subElement(uiAppElem, 'icon'); + iconElem.text = 'icon.png'; + let labelElem = subElement(uiAppElem, 'label'); + labelElem.text = appId; + + // print xml pretty + const xmlPretty = pd.xml(etree.write()); + return xmlPretty; + }) + + .then(xmlPretty => + Q.denodeify(fs.writeFile)(this.manifest_path, xmlPretty)); + } // addUiAppInManifest + + removeApp(appId) { + const app = this.getAppSync(appId); + + return Q.fcall(() => { + if (app == null) { // equals to ((=== null) or (=== undefined)) + throw new JsnError(appId + ' doesn\'t exist'); + } + }) + + .then(() => Q.denodeify(fs.readFile)(this.manifest_path)) + + .then(data => { + const etree = et.parse(data.toString()); + let root = etree.getroot(); + const uiAppElems = root.findall('./ui-application'); + if (uiAppElems) { + const len = uiAppElems.length; + for (let i = 0; i < len; i++) { + const uiApp = uiAppElems[i]; + if (uiApp.get('appid') == appId) { + root.remove(uiApp); + const xmlPretty = pd.xml(etree.write()); + return xmlPretty; + } + } + } + throw new JsnError('Can\'t get valid ui-application element' + + 'while parsing mafniest file'); + }) + + .then(xmlPretty => + Q.denodeify(fs.writeFile)(this.manifest_path, xmlPretty)) + + .then(() => { + const len = this.apps.length; + for (let i = 0; i < len; i++) { + const app = this.apps[i]; + if (app.app_id == appId) { + this.apps.splice(i, 1); + break; + } + } + this.has_app = (this.apps.length > 0) ? true : false; + }); + } // removeApp + + // it is not async, so adds 'sync' to the its name + getAppSync(appId) { + if (!this.has_app) + return null; + const len = this.apps.length; + for (let i = 0; i < len; i++) { + const app = this.apps[i]; + if (app.app_id == appId) + return app; + } + return null; + } // getAppSync +} // Parser + +/** + * This Parser module is a class. Parser contains actions such as parsing + * manifest file, and adding/removing 'ui-application' element. + * Parser does 'async' by 'Promises' with 'q'. + * (Reference to 'cli.js') + */ +module.exports = Parser; diff --git a/jsn-cli/lib/remove.js b/jsn-cli/lib/remove.js new file mode 100755 index 0000000..070573b --- /dev/null +++ b/jsn-cli/lib/remove.js @@ -0,0 +1,49 @@ +'use strict' + +const path = require('path'); +const Q = require('q'); + +const shellUtil = require('./shell_util'); +const JsnError = require('./jsn_error'); + +module.exports = function(argv, parser) { + const appId = argv.args_[0]; + + return Q.fcall(() => { + if (appId === undefined) + throw new JsnError('You should type appid'); + + return parser.parse() + .catch(err => { + throw new JsnError('Parsing manifest file has a problem: ' + + err.message); + }) + }) + + .then(() => { + console.log('Removing an app'); + if (parser.get('parsed') && parser.get('has_app')) { + const app = parser.getAppSync(appId); + if (app === null) { + throw new JsnError('There is no app'); + } + + console.log('Deleting directories & files'); + const execFileInBin = path.join(parser.get('exec_path'), + 'bin', app.exec); + const appDirInRes = path.join(parser.get('exec_path'), + 'res', app.app_id); + const shPromises = ['rm ' + execFileInBin, 'rm -rf ' + appDirInRes] + .map(shellUtil.shExec); + return Q.all(shPromises); + } else { + throw new JsnError('There is no app'); + } + }) + + .then(() => { + console.log('Deleting ui-application element in manifest file'); + return parser.removeApp(appId) + .then(() => console.log('Done to remove ' + appId)); + }); +}; diff --git a/jsn-cli/lib/shell_util.js b/jsn-cli/lib/shell_util.js new file mode 100644 index 0000000..57af07e --- /dev/null +++ b/jsn-cli/lib/shell_util.js @@ -0,0 +1,16 @@ +'use strict' + +const Q = require('q'); +const sh = require('shelljs'); + +const shellUtil = module.exports; + +// This is utility function for easing usage of shell script command. +shellUtil.shExec = cmd => { + const deferred = Q.defer(); + sh.exec(cmd, {async:true, silent:true}, (code) => { + if (code != 0) deferred.reject(cmd + ' returns ' + code); + else deferred.resolve(code); + }); + return deferred.promise; +}; \ No newline at end of file diff --git a/jsn-cli/lib/sign.js b/jsn-cli/lib/sign.js new file mode 100644 index 0000000..a2554f1 --- /dev/null +++ b/jsn-cli/lib/sign.js @@ -0,0 +1,116 @@ +'use strict' + +const fs = require('fs'); +const path = require('path'); +const Q = require('q'); + +const JsnError = require('./jsn_error'); +const shellUtil = require('./shell_util'); + +module.exports = sign; + +/** + * Sign command should be executed before build command. + * If you execute sign command after build command, the sign command + * deletes the tpk file which created by build command. + */ +function sign(argv, parser) { + const argc = argv.args_.length; + const signCmdArray = []; // it will be used as command + + return Q.fcall(() => { + // if argc is not 11, argc's num is equal or greater than 7 minimally + if (argc !== 7 && argc !== 11) { + throw new JsnError( + 'You should type sign command with proper arguments\n' + + 'Command you typed: ' + argv.args_); + } + }) + + .then(() => { + /** + * argv.args_ are parameters for sign cmd + * sign cmd: + * [signer] [targetDir] [authorCAPath] [authorP12Path] [authorPwd] + * [dist1P12Path] [dist1Pwd] [dist1CAPath] + * [dist2P12Path] [dist2Pwd] [dist2CAPath] [dist2RootPath] + * if argc is not 11, + * fill them(11 - num of empty parameters) with ' ' (one space string) + */ + + // caution: targetDir is not from args_ + const targetDir = parser.get('exec_path'); + const signer = argv.args_[0]; + const authorCAPath = argv.args_[1]; + const authorP12Path = argv.args_[2]; + const authorPwd = argv.args_[3]; + const dist1P12Path = argv.args_[4]; + const dist1Pwd = argv.args_[5]; + const dist1CAPath = argv.args_[6]; + const dist2P12Path = argv.args_[7] || '" "'; + const dist2Pwd = argv.args_[8] || '" "'; + const dist2CAPath = argv.args_[9] || '" "'; + const dist2RootPath = argv.args_[10] || '" "'; + + // verify arguments for sign command by checking wheter files exist + console.log('Verifing arguments for signing'); + let verifyArgv = [signer, targetDir, authorCAPath, authorP12Path, + dist1P12Path, dist1CAPath]; + if (argc === 11) { + verifyArgv = + verifyArgv.concat([dist2P12Path, dist2CAPath, dist2RootPath]); + } + + const promToExistFiles = verifyArgv.map(path => { + const deferred = Q.defer(); + fs.stat(path, (err, stats) => { + if (err) deferred.reject(err); + else deferred.resolve(stats); + }); + return deferred.promise; + }); + + return Q.all(promToExistFiles) + .then( + () => { + let signArgv = [signer, targetDir, authorCAPath, authorP12Path, + authorPwd, dist1P12Path, dist1Pwd, dist1CAPath]; + if (argc === 7) { + signArgv = signArgv.concat([dist2P12Path, dist2Pwd, + dist2CAPath, dist2RootPath]); + } + Array.prototype.push.apply(signCmdArray, signArgv); + }, err => { + throw new JsnError('Can\'t verify arguments: ' + err.message); + }) + }) + + .then(() => parser.parse()) + .catch(err => { + throw new JsnError('Parsing manifest file has a problem: ' + + err.message); + }) + + /** + * if [*.tpk, author-signature.xml, .manifest.tmp, signature1.xml] + * exists, remove all of them + */ + .then(() => { + console.log('Signing package'); + console.log('Checking previous signing'); + const shExecPromises = ['*.tpk', 'author-signature.xml', + '.manifest.tmp', 'signature1.xml'] + .map(dir => ('rm ' + path.join(parser.get('exec_path'), dir))) + .map(shellUtil.shExec); + return Q.all(shExecPromises) + // If above file doesn't exist, it can fail. But it needs to pass. + .fail(() => {}); + }) + + // try signing + .then(() => { + console.log('Trying signing from %s', signCmdArray[0]); + const signCmd = signCmdArray.join(' '); + return shellUtil.shExec(signCmd); + }); +} diff --git a/jsn-cli/package.json b/jsn-cli/package.json new file mode 100755 index 0000000..f218641 --- /dev/null +++ b/jsn-cli/package.json @@ -0,0 +1,23 @@ +{ + "name": "jsn-cli", + "version": "0.1.0", + "preferGlobal": "true", + "description": "command-line interface for js native runtime", + "main": "jsn-cli", + "bin": { + "jsn-cli": "./bin/jsn-cli" + }, + "engines": { "node": ">= 4.2.1" }, + "dependencies": { + "elementtree": "0.1.6", + "pretty-data": "0.40.0", + "q": "1.4.1", + "shelljs": "0.5.3", + "string-template": "0.2.1" + }, + "author": { "name": "Yongseop Kim", "email": "yons.kim@samsung.com" }, + "contributors": [ + { "name": "Yongseop Kim", "email": "yons.kim@samsung.com" }, + { "name": "Joonghyun Cho", "email": "jh5.cho@samsung.com" } + ] +} diff --git a/jsn-cli/tizen-app-template/icon.png b/jsn-cli/tizen-app-template/icon.png new file mode 100755 index 0000000..9765b1b Binary files /dev/null and b/jsn-cli/tizen-app-template/icon.png differ diff --git a/jsn-cli/tizen-app-template/index.js b/jsn-cli/tizen-app-template/index.js new file mode 100755 index 0000000..a0b992f --- /dev/null +++ b/jsn-cli/tizen-app-template/index.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node +module.paths = ['/usr/lib/js-binding/'].concat(module.paths); +var appfw = require('appfw'); + +appfw.on('create' , function() { + console.log('!! created'); +}); + +appfw.on('service' , function() { + console.log('!! service!!!'); +}); + +appfw.on('pause' , function() { + console.log('!! pause!!!'); +}); + +appfw.on('resume' , function() { + console.log('!! resume!!!'); +}); + +appfw.on('terminate' , function() { + console.log('!! terminate!!!'); +}); + +appfw.init('{appid}'); diff --git a/jsn-cli/tizen-app-template/pkgid.appid b/jsn-cli/tizen-app-template/pkgid.appid new file mode 100755 index 0000000..44d1da3 --- /dev/null +++ b/jsn-cli/tizen-app-template/pkgid.appid @@ -0,0 +1,3 @@ +#!/usr/bin/env node +module.paths = ['/usr/lib/js-binding/'].concat(module.paths); +require('../res/{appid}'); diff --git a/jsn-cli/tizen-app-template/tizen-manifest.xml b/jsn-cli/tizen-app-template/tizen-manifest.xml new file mode 100755 index 0000000..2662f25 --- /dev/null +++ b/jsn-cli/tizen-app-template/tizen-manifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/node-xwalk/CMakeLists.txt b/node-xwalk/CMakeLists.txt new file mode 100644 index 0000000..2c6ded1 --- /dev/null +++ b/node-xwalk/CMakeLists.txt @@ -0,0 +1,33 @@ +SET(TARGET_XWALK "node-xwalk") +SET(TARGET_XWALK_NATIVE "native") +SET(TARGET_XWALK_SRC ${PROJECT_SOURCE_DIR}/node-xwalk) + +# Copy Project +INSTALL(FILES + ${TARGET_XWALK_SRC}/package.json + DESTINATION ${GLOBAL_NODE_MODULE_PATH}/${TARGET_XWALK} +) + +INSTALL(FILES + ${TARGET_XWALK_SRC}/lib/loader.js + DESTINATION ${GLOBAL_NODE_MODULE_PATH}/${TARGET_XWALK}/lib +) + +# Native Binding Module +PKG_CHECK_MODULES(TARGET_XWALK_NATIVE_DEPS REQUIRED + nodejs +) + +INCLUDE_DIRECTORIES ( + ${TARGET_XWALK_SRC}/src + ${TARGET_XWALK_NATIVE_DEPS_INCLUDE_DIRS} +) +ADD_LIBRARY(${TARGET_XWALK_NATIVE} MODULE + ${TARGET_XWALK_SRC}/src/native_binding.cc + ${TARGET_XWALK_SRC}/src/extension.cc + ${TARGET_XWALK_SRC}/src/extension_adapter.cc +) +SET_TARGET_PROPERTIES(${TARGET_XWALK_NATIVE} PROPERTIES PREFIX "" SUFFIX ".node") +INSTALL(TARGETS ${TARGET_XWALK_NATIVE} DESTINATION + ${GLOBAL_NODE_MODULE_PATH}/${TARGET_XWALK}/build/Release) + diff --git a/node-xwalk/CPPLINT.cfg b/node-xwalk/CPPLINT.cfg new file mode 100644 index 0000000..51cbf64 --- /dev/null +++ b/node-xwalk/CPPLINT.cfg @@ -0,0 +1,3 @@ +set noparent +filter=-build/header_guard,-build/include +linelength=80 \ No newline at end of file diff --git a/node-xwalk/README.md b/node-xwalk/README.md new file mode 100644 index 0000000..cca6cb4 --- /dev/null +++ b/node-xwalk/README.md @@ -0,0 +1,46 @@ +## node-xwalk +Crosswalk Extension Loader for Node.js allows you to use crosswalk extensions in Node.js environment. + +* [Crosswalk Extension](https://github.com/crosswalk-project/crosswalk-website/wiki/Crosswalk-Extensions) + + Crosswalk Extensions allow exposing new functionality to JavaScript environment of your application. + This functionality can be implemented in native code. + * [Writing a Crosswalk Extension](https://github.com/crosswalk-project/crosswalk-website/wiki/Writing-a-Crosswalk-Extension) + * [Writing a GLib based Crosswalk Extension](https://github.com/crosswalk-project/crosswalk-website/wiki/Writing-a-glib-based-Crosswalk-Extension) + +Supported platforms: **Linux** | Other platforms will be supported soon. + +**Echo Sample** +```javascript +var xwalk = require('node-xwalk'); +var echo = xwalk.require('echo'); +echo.echo("Hello Async!!", function(msg) { + console.log(msg); +}); +console.log(echo.syncEcho("Hello Sync!!")); +``` +**Output** +``` +Instance 1 created! +Hello Async!! +Hello Sync!! +``` + +## Getting started +From your project directory, run +``` +$ npm install https://github.com/WonyoungChoi/node-xwalk +``` +This will download and build node-xwalk in ```./node_modules/```. +Then copy the example files from [examples/](https://github.com/WonyoungChoi/node-xwalk/tree/master/examples) directory in this project. + +Build and test a crosswalk extension *'echo example'* with Makefile. +``` +$ make +$ node echo.js +``` + +## License + +**node-xwalk** is available under the BSD licenses, see our `LICENSE` file. + diff --git a/node-xwalk/binding.gyp b/node-xwalk/binding.gyp new file mode 100644 index 0000000..059f8e3 --- /dev/null +++ b/node-xwalk/binding.gyp @@ -0,0 +1,15 @@ +{ + "targets": [ + { + "target_name": "native", + "sources": [ + "src/native_binding.cc", + "src/extension.cc", + "src/extension_adapter.cc" + ], + "include_dirs": [ + "src" + ] + } + ] +} \ No newline at end of file diff --git a/node-xwalk/examples/echo/Makefile b/node-xwalk/examples/echo/Makefile new file mode 100644 index 0000000..5e8f3a8 --- /dev/null +++ b/node-xwalk/examples/echo/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-fPIC -I../../src +LDFLAGS=--shared + +TARGET=libecho.so +SRCS=echo_extension.c + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) \ No newline at end of file diff --git a/node-xwalk/examples/echo/echo.js b/node-xwalk/examples/echo/echo.js new file mode 100644 index 0000000..f3f2c5b --- /dev/null +++ b/node-xwalk/examples/echo/echo.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +// Copyright (c) 2015 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. + +var xwalk = require('node-xwalk'); +var echo = xwalk.require('echo'); + +echo.echo("Hello Async!!", function(msg) { + console.log("Async callback: " + msg); +}); + +var ret = echo.syncEcho("Hello Sync!!"); +console.log("Sync return: " + ret); + diff --git a/node-xwalk/examples/echo/echo_extension.c b/node-xwalk/examples/echo/echo_extension.c new file mode 100644 index 0000000..78b7986 --- /dev/null +++ b/node-xwalk/examples/echo/echo_extension.c @@ -0,0 +1,70 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(__cplusplus) +#error "This file is written in C to make sure the C API works as intended." +#endif + +#include +#include +#include "xwalk/extensions/public/XW_Extension.h" +#include "xwalk/extensions/public/XW_Extension_SyncMessage.h" + +XW_Extension g_extension = 0; +const XW_CoreInterface* g_core = NULL; +const XW_MessagingInterface* g_messaging = NULL; +const XW_Internal_SyncMessagingInterface* g_sync_messaging = NULL; + +void instance_created(XW_Instance instance) { + printf("Instance %d created!\n", instance); +} + +void instance_destroyed(XW_Instance instance) { + printf("Instance %d destroyed!\n", instance); +} + +void handle_message(XW_Instance instance, const char* message) { + g_messaging->PostMessage(instance, message); +} + +void handle_sync_message(XW_Instance instance, const char* message) { + g_sync_messaging->SetSyncReply(instance, message); +} + +void shutdown(XW_Extension extension) { + printf("Shutdown\n"); +} + +int32_t XW_Initialize(XW_Extension extension, XW_GetInterface get_interface) { + static const char* kAPI = + "var echoListener = null;" + "extension.setMessageListener(function(msg) {" + " if (echoListener instanceof Function) {" + " echoListener(msg);" + " };" + "});" + "exports.echo = function(msg, callback) {" + " echoListener = callback;" + " extension.postMessage(msg);" + "};" + "exports.syncEcho = function(msg) {" + " return extension.internal.sendSyncMessage(msg);" + "};"; + + g_extension = extension; + g_core = get_interface(XW_CORE_INTERFACE); + g_core->SetExtensionName(extension, "echo"); + g_core->SetJavaScriptAPI(extension, kAPI); + g_core->RegisterInstanceCallbacks( + extension, instance_created, instance_destroyed); + g_core->RegisterShutdownCallback(extension, shutdown); + + g_messaging = get_interface(XW_MESSAGING_INTERFACE); + g_messaging->Register(extension, handle_message); + + g_sync_messaging = get_interface(XW_INTERNAL_SYNC_MESSAGING_INTERFACE); + g_sync_messaging->Register(extension, handle_sync_message); + + return XW_OK; +} diff --git a/node-xwalk/examples/echo/libecho.so b/node-xwalk/examples/echo/libecho.so new file mode 100755 index 0000000..f0737f5 Binary files /dev/null and b/node-xwalk/examples/echo/libecho.so differ diff --git a/node-xwalk/examples/echo_binary/Makefile b/node-xwalk/examples/echo_binary/Makefile new file mode 100644 index 0000000..1f3fe17 --- /dev/null +++ b/node-xwalk/examples/echo_binary/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-fPIC -I../../src +LDFLAGS=--shared + +TARGET=libecho2.so +SRCS=echo_extension_messaging_2.c + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) \ No newline at end of file diff --git a/node-xwalk/examples/echo_binary/echo2.js b/node-xwalk/examples/echo_binary/echo2.js new file mode 100644 index 0000000..2eabd64 --- /dev/null +++ b/node-xwalk/examples/echo_binary/echo2.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +// Copyright (c) 2015 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. + +var xwalk = require('node-xwalk'); +var echo2 = xwalk.require('echo2'); + +// Test async messaging +echo2.echo("Hello Async!!", function(msg) { + console.log(msg); + // Test async messaging with ArrayBuffer + var dataLength = 32; + var buffer = new ArrayBuffer(dataLength + 4); + var uint32View = new Uint32Array(buffer, 0, 1); + uint32View[0] = dataLength; + var uint8View = new Uint8Array(buffer, 4, dataLength); + for (var i=0; i < dataLength; i++) { + uint8View[i] = i; + } + echo2.echoBinary(buffer, function(msg) { + if (!(msg instanceof ArrayBuffer)) + throw "message is not binary."; + var retUint32View = new Uint32Array(msg, 0, 1); + var retLength = retUint32View[0]; + if (retLength != dataLength) + throw "message length doesn't match."; + var retUint8View = new Uint8Array(msg, 4, retLength); + console.log(retUint8View); + }); +}); + diff --git a/node-xwalk/examples/echo_binary/echo_extension_messaging_2.c b/node-xwalk/examples/echo_binary/echo_extension_messaging_2.c new file mode 100644 index 0000000..d19cb72 --- /dev/null +++ b/node-xwalk/examples/echo_binary/echo_extension_messaging_2.c @@ -0,0 +1,79 @@ +// Copyright (c) 2015 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(__cplusplus) +#error "This file is written in C to make sure the C API works as intended." +#endif + +#include +#include +#include "xwalk/extensions/public/XW_Extension.h" +#include "xwalk/extensions/public/XW_Extension_Message_2.h" + +XW_Extension g_extension = 0; +const XW_CoreInterface* g_core = NULL; +const XW_MessagingInterface2* g_messaging_2 = NULL; + +void instance_created(XW_Instance instance) { + printf("Instance %d created!\n", instance); +} + +void instance_destroyed(XW_Instance instance) { + printf("Instance %d destroyed!\n", instance); +} + +void handle_message(XW_Instance instance, const char* message) { + g_messaging_2->PostMessage(instance, message); +} + +void handle_binary_message( + XW_Instance instance, const char* message, const size_t size) { + g_messaging_2->PostBinaryMessage(instance, message, size); +} + +void shutdown(XW_Extension extension) { + printf("Shutdown\n"); +} + +int32_t XW_Initialize(XW_Extension extension, XW_GetInterface get_interface) { + static const char* kAPI = + "var echoListener = null;" + "var echoBinaryListener = null;" + "extension.setMessageListener(function(msg) {" + " if (echoListener instanceof Function) {" + " if (msg instanceof ArrayBuffer) {" + " echoBinaryListener(msg);" + " } else {" + " echoListener(msg);" + " }" + " }" + "});" + "exports.echo = function(msg, callback) {" + " echoListener = callback;" + " extension.postMessage(msg);" + "};" + "exports.echoBinary = function(msg, callback) {" + " echoBinaryListener = callback;" + " extension.postMessage(msg);" + "};"; + + g_extension = extension; + g_core = get_interface(XW_CORE_INTERFACE); + if (g_core == NULL) + return XW_ERROR; + g_core->SetExtensionName(extension, "echo2"); + g_core->SetJavaScriptAPI(extension, kAPI); + g_core->RegisterInstanceCallbacks( + extension, instance_created, instance_destroyed); + g_core->RegisterShutdownCallback(extension, shutdown); + + g_messaging_2 = get_interface(XW_MESSAGING_INTERFACE_2); + if (g_messaging_2 == NULL) + return XW_ERROR; + g_messaging_2->Register(extension, handle_message); + g_messaging_2->RegisterBinaryMesssageCallback( + extension, handle_binary_message); + + return XW_OK; +} diff --git a/node-xwalk/examples/echo_binary/libecho2.so b/node-xwalk/examples/echo_binary/libecho2.so new file mode 100755 index 0000000..abe17dd Binary files /dev/null and b/node-xwalk/examples/echo_binary/libecho2.so differ diff --git a/node-xwalk/lib/loader.js b/node-xwalk/lib/loader.js new file mode 100644 index 0000000..ff63d84 --- /dev/null +++ b/node-xwalk/lib/loader.js @@ -0,0 +1,123 @@ +// Copyright (c) 2015 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. + +var fs = require('fs'); +var path = require('path'); +var process = require('process'); + +var native_ = require("../build/Release/native.node"); + +function error(msg) { + console.error("ERR: " + msg); +} + +// Cached set of extension modules +var extensions_ = {}; + +var ExtensionModule = function(ext_name, ext_path) { + this.extension_name = ext_name; + this.extension_path = ext_path; +}; + +ExtensionModule.prototype.load = function() { + this.extension_info = native_.getExtensionInfo(this.extension_path); + if (!this.extension_info) { + error('Error during get information of extension "' + + this.extension_name + '"'); + return false; + } + + if (this.extension_info.name !== this.extension_name) { + error('Extension name does not match. ' + + 'required = ' + this.extension_name + + ', loaded = ' + this.extension_info.name); + return false; + } + + this.instance_id = native_.createInstance(this.extension_info.extension_id); + if (!this.instance_id) { + error('Error during creating instance of extension "' + + this.extension_name + '"'); + return false; + } + + var jscode = + '(function(extension) {' + + ' extension.internal = { instance_id: extension.instance_id };' + + ' extension.internal.sendSyncMessage = extension.sendSyncMessage;' + + ' delete extension.sendSyncMessage;' + + ' var exports = {}; ' + + ' (function() {\'use strict\'; ' + this.extension_info.jsapi + '})();' + + ' return exports;' + + '});'; + try { + var func = eval(jscode); + this.instance = func({ + instance_id : this.instance_id, + postMessage: function(msg) { + native_.postMessage(this.instance_id, msg); + }, + sendSyncMessage: function(msg) { + return native_.sendSyncMessage(this.instance_id, msg); + }, + setMessageListener: function(fn) { + native_.setMessageListener(this.instance_id, fn); + } + }); + return true; + } catch (err) { + error('Error during loading extension "' + + this.extension_name + '"'); + return false; + } + + return false; +}; + +var ExtensionLoader = function() { + this.extension_paths = [ + process.cwd(), + path.join(process.cwd(), 'xwalk_extensions') + ]; +}; + +ExtensionLoader.prototype.findExtensionInPath = function(name) { + for (var i in this.extension_paths) { + var ext_path = path.join(this.extension_paths[i], + 'lib' + name + '.so'); + try { + fs.accessSync(ext_path, fs.R_OK); + return ext_path; + } catch (err) { + continue; + } + } + return undefined; +}; + +ExtensionLoader.prototype.setRuntimeVariable = function(key, value) { + native_.setRuntimeVariable(key, value); +}; + +ExtensionLoader.prototype.require = function(name) { + if (extensions_.hasOwnProperty(name)) { + return extensions_[name].instance; + } + + var ext_path = this.findExtensionInPath(name); + if (!ext_path) { + error('Can not find extension "' + name + '"'); + return undefined; + } + + var ext = new ExtensionModule(name, ext_path); + if (ext.load()) { + extensions_[name] = ext; + return extensions_[name].instance; + } + + return undefined; +}; + +module.exports = new ExtensionLoader; diff --git a/node-xwalk/package.json b/node-xwalk/package.json new file mode 100644 index 0000000..54b2e17 --- /dev/null +++ b/node-xwalk/package.json @@ -0,0 +1,13 @@ +{ + "name": "node-xwalk", + "version": "0.9.0", + "description": "Crosswalk extension loader for Node.JS", + "main": "./lib/loader.js", + "author": { + "name": "Wonyoung Choi", + "email": "wy80.choi@samsung.com" + }, + "scripts": { + "install": "node-gyp rebuild" + } +} \ No newline at end of file diff --git a/node-xwalk/packaging/node-xwalk.manifest b/node-xwalk/packaging/node-xwalk.manifest new file mode 100644 index 0000000..75b0fa5 --- /dev/null +++ b/node-xwalk/packaging/node-xwalk.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/node-xwalk/packaging/node-xwalk.spec b/node-xwalk/packaging/node-xwalk.spec new file mode 100644 index 0000000..14b2a78 --- /dev/null +++ b/node-xwalk/packaging/node-xwalk.spec @@ -0,0 +1,45 @@ +Name: node-xwalk +Summary: Crosswalk extension loader for Node.JS +Version: 0.9.0 +Release: 1 +Group: Development/Libraries +License: BSD-3-Clause +URL: https:://github.com/WonyoungChoi/node-xwalk +Source0: %{name}-%{version}.tar.gz +Source1: %{name}.manifest + +BuildRequires: cmake +BuildRequires: pkgconfig(nodejs) +Requires: nodejs + +%description +Crosswalk Extension Loader for Node.JS allows you +to use crosswalk extensions in Node.JS environment. + +%prep +%setup -q +cp %{SOURCE1} . + +%build +mkdir -p cmake_build_tmp +cd cmake_build_tmp + +cmake .. \ + -DCMAKE_INSTALL_PREFIX=%{prefix} + +make %{?jobs:-j%jobs} + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/usr/share/license +cp LICENSE %{buildroot}/usr/share/license/%{name} +cd cmake_build_tmp +%make_install + +%clean +rm -rf %{buildroot} + +%files +%manifest %{name}.manifest +%{_datadir}/license/%{name} +/usr/local/lib/node_modules/%{name}/* diff --git a/node-xwalk/src/extension.cc b/node-xwalk/src/extension.cc new file mode 100644 index 0000000..69ca62d --- /dev/null +++ b/node-xwalk/src/extension.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Copyright (c) 2015 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 "extension.h" + +#include +#include + +#include "extension_adapter.h" +#include "log.h" + +namespace xwalk { +namespace extensions { + +Extension::Extension(const std::string& path) + : initialized_(false), + library_path_(path), + xw_extension_(0), + created_instance_callback_(NULL), + destroyed_instance_callback_(NULL), + shutdown_callback_(NULL), + handle_msg_callback_(NULL), + handle_sync_msg_callback_(NULL), + handle_binary_msg_callback_(NULL) { +} + +Extension::~Extension() { + if (!initialized_) + return; + + if (shutdown_callback_) + shutdown_callback_(xw_extension_); + ExtensionAdapter::GetInstance()->UnregisterExtension(this); +} + +bool Extension::Initialize() { + if (initialized_) + return true; + + void* handle = dlopen(library_path_.c_str(), RTLD_LAZY); + if (!handle) { + LOGE("Error loading extension '%s' : %s", + library_path_.c_str(), dlerror()); + return false; + } + + XW_Initialize_Func initialize = reinterpret_cast( + dlsym(handle, "XW_Initialize")); + if (!initialize) { + LOGE("Error loading extension '%s' : " + "couldn't get XW_Initialize function", + library_path_.c_str()); + dlclose(handle); + return false; + } + + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + xw_extension_ = adapter->GetNextXWExtension(); + adapter->RegisterExtension(this); + + int ret = initialize(xw_extension_, ExtensionAdapter::GetInterface); + if (ret != XW_OK) { + LOGE("Error loading extension '%s' : " + "XW_Initialize function returned error value.", + library_path_.c_str()); + dlclose(handle); + return false; + } + + initialized_ = true; + return true; +} + +ExtensionInstance* Extension::CreateInstance() { + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + XW_Instance xw_instance = adapter->GetNextXWInstance(); + return new ExtensionInstance(this, xw_instance); +} + +void Extension::GetRuntimeVariable(const char* key, + char* value, + size_t value_len) { + if (runtime_variable_callback_) { + std::string ret = runtime_variable_callback_(key); + strncpy(value, ret.data(), value_len); + } +} + +int Extension::CheckAPIAccessControl(const char* /*api_name*/) { + // Not Supported + return XW_OK; +} + +int Extension::RegisterPermissions(const char* /*perm_table*/) { + // Not Supported + return XW_OK; +} + +ExtensionInstance::ExtensionInstance(Extension* extension, + XW_Instance xw_instance) + : extension_(extension), + xw_instance_(xw_instance), + instance_data_(NULL) { + ExtensionAdapter::GetInstance()->RegisterInstance(this); + XW_CreatedInstanceCallback callback = + extension_->created_instance_callback_; + if (callback) + callback(xw_instance_); +} + +ExtensionInstance::~ExtensionInstance() { + XW_DestroyedInstanceCallback callback = + extension_->destroyed_instance_callback_; + if (callback) + callback(xw_instance_); + ExtensionAdapter::GetInstance()->UnregisterInstance(this); +} + +void ExtensionInstance::HandleMessage(const std::string& msg) { + XW_HandleMessageCallback callback = + extension_->handle_msg_callback_; + if (callback) + callback(xw_instance_, msg.c_str()); +} + +void ExtensionInstance::HandleMessage(const char* msg, const size_t size) { + XW_HandleBinaryMessageCallback callback = + extension_->handle_binary_msg_callback_; + if (callback) + callback(xw_instance_, msg, size); +} + +void ExtensionInstance::HandleSyncMessage(const std::string& msg) { + XW_HandleSyncMessageCallback callback = + extension_->handle_sync_msg_callback_; + if (callback) { + callback(xw_instance_, msg.c_str()); + } +} + +void ExtensionInstance::PostMessageToJS(const std::string& msg) { + if (post_message_callback_) { + post_message_callback_(msg.c_str(), 0, false); + } +} + +void ExtensionInstance::PostMessageToJS(const char* msg, size_t size) { + if (post_message_callback_) { + post_message_callback_(msg, size, true); + } +} + +void ExtensionInstance::SyncReplyToJS(const std::string& reply) { + if (send_sync_reply_callback_) { + send_sync_reply_callback_(reply.c_str(), 0, false); + } +} + +} // namespace extensions +} // namespace xwalk diff --git a/node-xwalk/src/extension.h b/node-xwalk/src/extension.h new file mode 100644 index 0000000..cb89c17 --- /dev/null +++ b/node-xwalk/src/extension.h @@ -0,0 +1,132 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Copyright (c) 2015 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 NODE_XWALK_EXTENSIONS_EXTENSION_H_ +#define NODE_XWALK_EXTENSIONS_EXTENSION_H_ + +#include +#include +#include +#include + +#include "xwalk/extensions/public/XW_Extension.h" +#include "xwalk/extensions/public/XW_Extension_Message_2.h" +#include "xwalk/extensions/public/XW_Extension_SyncMessage.h" + +namespace xwalk { +namespace extensions { + +class ExtensionAdapter; +class ExtensionInstance; + +class Extension { + public: + typedef std::function + RuntimeVariableCallback; + + explicit Extension(const std::string& path); + virtual ~Extension(); + + bool Initialize(); + ExtensionInstance* CreateInstance(); + + XW_Extension xw_extension() const { + return xw_extension_; + } + + std::string name() const { + return name_; + } + + std::string javascript_api() const { + return javascript_api_; + } + + std::vector& entry_points() { + return entry_points_; + } + + void set_name(const std::string& name) { + name_ = name; + } + + void set_javascript_api(const std::string& javascript_api) { + javascript_api_ = javascript_api; + } + + void set_runtime_variable_callback(RuntimeVariableCallback callback) { + runtime_variable_callback_ = callback; + } + + private: + friend class ExtensionAdapter; + friend class ExtensionInstance; + + void GetRuntimeVariable(const char* key, char* value, size_t value_len); + int CheckAPIAccessControl(const char* api_name); + int RegisterPermissions(const char* perm_table); + + bool initialized_; + std::string library_path_; + XW_Extension xw_extension_; + std::string name_; + std::string javascript_api_; + std::vector entry_points_; + + RuntimeVariableCallback runtime_variable_callback_; + + XW_CreatedInstanceCallback created_instance_callback_; + XW_DestroyedInstanceCallback destroyed_instance_callback_; + XW_ShutdownCallback shutdown_callback_; + XW_HandleMessageCallback handle_msg_callback_; + XW_HandleSyncMessageCallback handle_sync_msg_callback_; + XW_HandleBinaryMessageCallback handle_binary_msg_callback_; +}; + +class ExtensionInstance { + public: + typedef std::function + MessageCallback; + + ExtensionInstance(Extension* extension, XW_Instance xw_instance); + virtual ~ExtensionInstance(); + + void HandleMessage(const std::string& msg); + void HandleMessage(const char* msg, const size_t size); + void HandleSyncMessage(const std::string& msg); + + XW_Instance xw_instance() const { + return xw_instance_; + } + + void set_post_message_listener(MessageCallback callback) { + post_message_callback_ = callback; + } + + void set_send_sync_message_listener(MessageCallback callback) { + send_sync_reply_callback_ = callback; + } + + private: + friend class ExtensionAdapter; + + void PostMessageToJS(const std::string& msg); + void PostMessageToJS(const char* msg, size_t size); + void SyncReplyToJS(const std::string& reply); + + Extension* extension_; + XW_Instance xw_instance_; + void* instance_data_; + + MessageCallback post_message_callback_; + MessageCallback send_sync_reply_callback_; +}; + +} // namespace extensions +} // namespace xwalk + + +#endif // NODE_XWALK_EXTENSIONS_EXTENSION_H_ diff --git a/node-xwalk/src/extension_adapter.cc b/node-xwalk/src/extension_adapter.cc new file mode 100644 index 0000000..a5559c8 --- /dev/null +++ b/node-xwalk/src/extension_adapter.cc @@ -0,0 +1,304 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Copyright (c) 2015 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 "extension_adapter.h" + +#include + +#include + +#include "log.h" + +namespace xwalk { +namespace extensions { + +ExtensionAdapter::ExtensionAdapter() + : next_xw_extension_(1), + next_xw_instance_(1) { +} + +ExtensionAdapter::~ExtensionAdapter() { +} + +ExtensionAdapter* ExtensionAdapter::GetInstance() { + static ExtensionAdapter self; + return &self; +} + +XW_Extension ExtensionAdapter::GetNextXWExtension() { + return next_xw_extension_++; +} + +XW_Instance ExtensionAdapter::GetNextXWInstance() { + return next_xw_instance_++; +} + +void ExtensionAdapter::RegisterExtension(Extension* extension) { + XW_Extension xw_extension = extension->xw_extension(); + if (!(xw_extension > 0 && xw_extension < next_xw_extension_)) { + LOGW("xw_extension (%d) is invalid.", xw_extension); + return; + } + if (extension_map_.find(xw_extension) == extension_map_.end()) + extension_map_[xw_extension] = extension; +} + +void ExtensionAdapter::UnregisterExtension(Extension* extension) { + XW_Extension xw_extension = extension->xw_extension(); + if (!(xw_extension > 0 && xw_extension < next_xw_extension_)) { + LOGW("xw_extension (%d) is invalid.", xw_extension); + return; + } + if (extension_map_.find(xw_extension) != extension_map_.end()) + extension_map_.erase(xw_extension); +} + +void ExtensionAdapter::RegisterInstance(ExtensionInstance* instance) { + XW_Instance xw_instance = instance->xw_instance(); + if (!(xw_instance > 0 && xw_instance < next_xw_instance_)) { + LOGW("xw_instance (%d) is invalid.", xw_instance); + return; + } + if (instance_map_.find(xw_instance) == instance_map_.end()) + instance_map_[xw_instance] = instance; +} + +void ExtensionAdapter::UnregisterInstance(ExtensionInstance* instance) { + XW_Instance xw_instance = instance->xw_instance(); + if (!(xw_instance > 0 && xw_instance < next_xw_instance_)) { + LOGW("xw_instance (%d) is invalid.", xw_instance); + return; + } + if (instance_map_.find(xw_instance) != instance_map_.end()) + instance_map_.erase(xw_instance); +} + +const void* ExtensionAdapter::GetInterface(const char* name) { + if (!strcmp(name, XW_CORE_INTERFACE_1)) { + static const XW_CoreInterface_1 coreInterface1 = { + CoreSetExtensionName, + CoreSetJavaScriptAPI, + CoreRegisterInstanceCallbacks, + CoreRegisterShutdownCallback, + CoreSetInstanceData, + CoreGetInstanceData + }; + return &coreInterface1; + } + + if (!strcmp(name, XW_MESSAGING_INTERFACE_1)) { + static const XW_MessagingInterface_1 messagingInterface1 = { + MessagingRegister, + MessagingPostMessage + }; + return &messagingInterface1; + } + + if (!strcmp(name, XW_MESSAGING_INTERFACE_2)) { + static const XW_MessagingInterface_2 messagingInterface2 = { + MessagingRegister, + MessagingPostMessage, + MessagingRegisterBinaryMessageCallback, + MessagingPostBinaryMessage + }; + return &messagingInterface2; + } + + if (!strcmp(name, XW_INTERNAL_SYNC_MESSAGING_INTERFACE_1)) { + static const XW_Internal_SyncMessagingInterface_1 + syncMessagingInterface1 = { + SyncMessagingRegister, + SyncMessagingSetSyncReply + }; + return &syncMessagingInterface1; + } + + if (!strcmp(name, XW_INTERNAL_ENTRY_POINTS_INTERFACE_1)) { + static const XW_Internal_EntryPointsInterface_1 entryPointsInterface1 = { + EntryPointsSetExtraJSEntryPoints + }; + return &entryPointsInterface1; + } + + if (!strcmp(name, XW_INTERNAL_RUNTIME_INTERFACE_1)) { + static const XW_Internal_RuntimeInterface_1 runtimeInterface1 = { + RuntimeGetStringVariable + }; + return &runtimeInterface1; + } + + if (!strcmp(name, XW_INTERNAL_PERMISSIONS_INTERFACE_1)) { + static const XW_Internal_PermissionsInterface_1 permissionsInterface1 = { + PermissionsCheckAPIAccessControl, + PermissionsRegisterPermissions + }; + return &permissionsInterface1; + } + + LOGW("Interface '%s' is not supported.", name); + return NULL; +} + +Extension* ExtensionAdapter::GetExtension(XW_Extension xw_extension) { + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + ExtensionMap::iterator it = adapter->extension_map_.find(xw_extension); + if (it == adapter->extension_map_.end()) + return NULL; + return it->second; +} + +ExtensionInstance* ExtensionAdapter::GetExtensionInstance( + XW_Instance xw_instance) { + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + InstanceMap::iterator it = adapter->instance_map_.find(xw_instance); + if (it == adapter->instance_map_.end()) + return NULL; + return it->second; +} + +#define CHECK(x, xw) \ + if (!x) { \ + LOGW("Ignoring call. Invalid %s = %d", #xw, xw); \ + return; \ + } + +#define RETURN_IF_INITIALIZED(x) \ + if (x->initialized_) \ + return; + +void ExtensionAdapter::CoreSetExtensionName(XW_Extension xw_extension, + const char* name) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->name_ = name; +} + +void ExtensionAdapter::CoreSetJavaScriptAPI(XW_Extension xw_extension, + const char* javascript_api) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->javascript_api_ = javascript_api; +} + +void ExtensionAdapter::CoreRegisterInstanceCallbacks(XW_Extension xw_extension, + XW_CreatedInstanceCallback created, + XW_DestroyedInstanceCallback destroyed) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->created_instance_callback_ = created; + extension->destroyed_instance_callback_ = destroyed; +} + +void ExtensionAdapter::CoreRegisterShutdownCallback(XW_Extension xw_extension, + XW_ShutdownCallback shutdown) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->shutdown_callback_ = shutdown; +} + +void ExtensionAdapter::CoreSetInstanceData(XW_Instance xw_instance, + void* data) { + ExtensionInstance* instance = GetExtensionInstance(xw_instance); + CHECK(instance, xw_instance); + instance->instance_data_ = data; +} + +void* ExtensionAdapter::CoreGetInstanceData(XW_Instance xw_instance) { + ExtensionInstance* instance = GetExtensionInstance(xw_instance); + if (instance) + return instance->instance_data_; + else + return NULL; +} + +void ExtensionAdapter::MessagingRegister(XW_Extension xw_extension, + XW_HandleMessageCallback handle_message) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->handle_msg_callback_ = handle_message; +} + +void ExtensionAdapter::MessagingPostMessage(XW_Instance xw_instance, + const char* message) { + ExtensionInstance* instance = GetExtensionInstance(xw_instance); + CHECK(instance, xw_instance); + instance->PostMessageToJS(message); +} + +void ExtensionAdapter::MessagingRegisterBinaryMessageCallback( + XW_Extension xw_extension, XW_HandleBinaryMessageCallback handle_message) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->handle_binary_msg_callback_ = handle_message; +} + +void ExtensionAdapter::MessagingPostBinaryMessage( + XW_Instance xw_instance, const char* message, size_t size) { + ExtensionInstance* instance = GetExtensionInstance(xw_instance); + CHECK(instance, xw_instance); + instance->PostMessageToJS(message, size); +} + +void ExtensionAdapter::SyncMessagingRegister(XW_Extension xw_extension, + XW_HandleSyncMessageCallback handle_sync_message) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + extension->handle_sync_msg_callback_ = handle_sync_message; +} + +void ExtensionAdapter::SyncMessagingSetSyncReply(XW_Instance xw_instance, + const char* reply) { + ExtensionInstance* instance = GetExtensionInstance(xw_instance); + CHECK(instance, xw_instance); + instance->SyncReplyToJS(reply); +} + +void ExtensionAdapter::EntryPointsSetExtraJSEntryPoints( + XW_Extension xw_extension, const char** entry_points) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + RETURN_IF_INITIALIZED(extension); + + for (int i=0; entry_points[i]; ++i) { + extension->entry_points_.push_back(std::string(entry_points[i])); + } +} + +void ExtensionAdapter::RuntimeGetStringVariable(XW_Extension xw_extension, + const char* key, + char* value, + unsigned int value_len) { + Extension* extension = GetExtension(xw_extension); + CHECK(extension, xw_extension); + extension->GetRuntimeVariable(key, value, value_len); +} + +int ExtensionAdapter::PermissionsCheckAPIAccessControl( + XW_Extension xw_extension, const char* api_name) { + Extension* extension = GetExtension(xw_extension); + if (extension) + return extension->CheckAPIAccessControl(api_name); + else + return XW_ERROR; +} + +int ExtensionAdapter::PermissionsRegisterPermissions(XW_Extension xw_extension, + const char* perm_table) { + Extension* extension = GetExtension(xw_extension); + if (extension) + return extension->RegisterPermissions(perm_table); + else + return XW_ERROR; +} + +} // namespace extensions +} // namespace xwalk diff --git a/node-xwalk/src/extension_adapter.h b/node-xwalk/src/extension_adapter.h new file mode 100644 index 0000000..9b894a0 --- /dev/null +++ b/node-xwalk/src/extension_adapter.h @@ -0,0 +1,102 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Copyright (c) 2015 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 NODE_XWALK_EXTENSIONS_EXTENSION_ADAPTER_H_ +#define NODE_XWALK_EXTENSIONS_EXTENSION_ADAPTER_H_ + +#include + +#include "xwalk/extensions/public/XW_Extension.h" +#include "xwalk/extensions/public/XW_Extension_Message_2.h" +#include "xwalk/extensions/public/XW_Extension_SyncMessage.h" +#include "xwalk/extensions/public/XW_Extension_EntryPoints.h" +#include "xwalk/extensions/public/XW_Extension_Runtime.h" +#include "xwalk/extensions/public/XW_Extension_Permissions.h" + +#include "extension.h" + +namespace xwalk { +namespace extensions { + +class ExtensionAdapter { + public: + static ExtensionAdapter* GetInstance(); + + static Extension* GetExtension(XW_Extension xw_extension); + static ExtensionInstance* GetExtensionInstance(XW_Instance xw_instance); + + XW_Extension GetNextXWExtension(); + XW_Instance GetNextXWInstance(); + + void RegisterExtension(Extension* extension); + void UnregisterExtension(Extension* extension); + + void RegisterInstance(ExtensionInstance* instance); + void UnregisterInstance(ExtensionInstance* instance); + + // Returns the correct struct according to interface asked. This is + // passed to external extensions in XW_Initialize() call. + static const void* GetInterface(const char* name); + + private: + ExtensionAdapter(); + virtual ~ExtensionAdapter(); + + // Core Interface + static void CoreSetExtensionName( + XW_Extension xw_extension, const char* name); + static void CoreSetJavaScriptAPI( + XW_Extension xw_extension, const char* javascript_api); + static void CoreRegisterInstanceCallbacks( + XW_Extension xw_extension, + XW_CreatedInstanceCallback created, + XW_DestroyedInstanceCallback destroyed); + static void CoreRegisterShutdownCallback( + XW_Extension xw_extension, XW_ShutdownCallback shutdown); + static void CoreSetInstanceData( + XW_Instance xw_instance, void* data); + static void* CoreGetInstanceData(XW_Instance xw_instance); + // Messaging Interface + static void MessagingRegister( + XW_Extension xw_extension, XW_HandleMessageCallback handle_message); + static void MessagingPostMessage( + XW_Instance xw_instance, const char* message); + static void MessagingRegisterBinaryMessageCallback( + XW_Extension extension, XW_HandleBinaryMessageCallback handle_message); + static void MessagingPostBinaryMessage( + XW_Instance instance, const char* message, size_t size); + // SyncMessage Interface + static void SyncMessagingRegister( + XW_Extension xw_extension, + XW_HandleSyncMessageCallback handle_sync_message); + static void SyncMessagingSetSyncReply( + XW_Instance xw_instance, const char* reply); + // EntryPoints Interface + static void EntryPointsSetExtraJSEntryPoints( + XW_Extension xw_extension, const char** entry_points); + // Runtime Interface + static void RuntimeGetStringVariable( + XW_Extension xw_extension, + const char* key, char* value, unsigned int value_len); + // Permission Interface + static int PermissionsCheckAPIAccessControl( + XW_Extension xw_extension, const char* api_name); + static int PermissionsRegisterPermissions( + XW_Extension xw_extension, const char* perm_table); + + typedef std::map ExtensionMap; + ExtensionMap extension_map_; + + typedef std::map InstanceMap; + InstanceMap instance_map_; + + XW_Extension next_xw_extension_; + XW_Instance next_xw_instance_; +}; + +} // namespace extensions +} // namespace xwalk + +#endif // NODE_XWALK_EXTENSIONS_EXTENSION_ADAPTER_H_ diff --git a/node-xwalk/src/log.h b/node-xwalk/src/log.h new file mode 100644 index 0000000..6b88f92 --- /dev/null +++ b/node-xwalk/src/log.h @@ -0,0 +1,15 @@ +// Copyright (c) 2015 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 NODE_XWALK_EXTENSIONS_LOG_H_ +#define NODE_XWALK_EXTENSIONS_LOG_H_ + +#include + +#define LOGD(fmt, args...) printf(fmt "\n", ##args) +#define LOGI(fmt, args...) printf(fmt "\n", ##args) +#define LOGW(fmt, args...) printf(fmt "\n", ##args) +#define LOGE(fmt, args...) printf(fmt "\n", ##args) + +#endif // NODE_XWALK_EXTENSIONS_LOG_H_ diff --git a/node-xwalk/src/native_binding.cc b/node-xwalk/src/native_binding.cc new file mode 100644 index 0000000..275f483 --- /dev/null +++ b/node-xwalk/src/native_binding.cc @@ -0,0 +1,264 @@ +// Copyright (c) 2015 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 "native_binding.h" + +#include +#include + +#include "extension.h" +#include "extension_adapter.h" +#include "log.h" + +namespace xwalk { +namespace extensions { + +namespace { + +// Cached function callbackes for each instance id +static std::map> g_listeners; + +// Cached runtime variables +static std::map g_runtime_variables; + +} // namespace + +// static +void NativeBinding::Init(v8::Handle target) { + NODE_SET_METHOD(target, "getExtensionInfo", GetExtensionInfo); + NODE_SET_METHOD(target, "createInstance", CreateInstance); + NODE_SET_METHOD(target, "destroyInstance", DestroyInstance); + NODE_SET_METHOD(target, "postMessage", PostMessage); + NODE_SET_METHOD(target, "sendSyncMessage", SendSyncMessage); + NODE_SET_METHOD(target, "setMessageListener", SetMessageListener); + NODE_SET_METHOD(target, "setRuntimeVariable", SetRuntimeVariable); +} + +// static +void NativeBinding::GetExtensionInfo( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 1 || !args[0]->IsString()) { + result.Set(v8::Undefined(isolate)); + return; + } + + v8::String::Utf8Value ext_path(args[0]->ToString()); + + Extension* ext = new Extension(std::string(*ext_path)); + if (ext && ext->Initialize()) { + auto rv_cb = [](const std::string& key) -> std::string { + auto it = g_runtime_variables.find(key); + if (it == g_runtime_variables.end()) { + return std::string(""); + } else { + return it->second; + } + }; + ext->set_runtime_variable_callback(rv_cb); + v8::Local obj = v8::Object::New(isolate); + obj->Set(v8::String::NewFromUtf8(isolate, "extension_id"), + v8::Integer::New(isolate, ext->xw_extension())); + obj->Set(v8::String::NewFromUtf8(isolate, "name"), + v8::String::NewFromUtf8(isolate, ext->name().c_str())); + obj->Set(v8::String::NewFromUtf8(isolate, "jsapi"), + v8::String::NewFromUtf8(isolate, ext->javascript_api().c_str())); + result.Set(obj); + } else { + LOGE("Failed to create extension, path=%s", *ext_path); + result.Set(v8::Undefined(isolate)); + return; + } +} + +// static +void NativeBinding::CreateInstance( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 1 || !args[0]->IsNumber()) { + result.Set(v8::Undefined(isolate)); + return; + } + + int ext_id = args[0]->Int32Value(); + Extension* ext = ExtensionAdapter::GetInstance()->GetExtension(ext_id); + if (!ext) { + LOGE("Can't find extension. extension_id=%d", ext_id); + result.Set(v8::Undefined(isolate)); + return; + } + + ExtensionInstance* instance = ext->CreateInstance(); + if (!instance) { + LOGE("Failed to create instance. extension_id=%d", ext_id); + result.Set(v8::Undefined(isolate)); + return; + } + + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + instance->set_post_message_listener( + std::bind(NativeBinding::PostMessageToJSCallback, + _1, _2, _3, instance->xw_instance())); + + result.Set(v8::Integer::New(isolate, instance->xw_instance())); +} + +// static +void NativeBinding::DestroyInstance( + const v8::FunctionCallbackInfo& /*args*/) { + // Nothing to do. + // Destroy Instance callbacks will be called when each extension is freed. +} + +// static +void NativeBinding::PostMessage( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 2 || !args[0]->IsNumber()) { + result.Set(v8::Undefined(isolate)); + return; + } + + int instance_id = args[0]->Int32Value(); + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + ExtensionInstance* instance = adapter->GetExtensionInstance(instance_id); + if (!instance) { + LOGE("Can't find instance. instance_id=%d", instance_id); + result.Set(v8::Undefined(isolate)); + return; + } + + if (args[1]->IsString()) { + v8::String::Utf8Value msg(args[1]->ToString()); + instance->HandleMessage(std::string(*msg)); + } else if (args[1]->IsArrayBuffer()) { + v8::Handle buffer = + v8::Handle::Cast(args[1]); + v8::ArrayBuffer::Contents contents = buffer->Externalize(); + void* ptr = contents.Data(); + instance->HandleMessage(static_cast(ptr), + contents.ByteLength()); + std::free(ptr); + } +} + +// static +void NativeBinding::SendSyncMessage( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 2 || !args[0]->IsNumber()) { + result.Set(v8::Undefined(isolate)); + return; + } + + int instance_id = args[0]->Int32Value(); + ExtensionAdapter* adapter = ExtensionAdapter::GetInstance(); + ExtensionInstance* instance = adapter->GetExtensionInstance(instance_id); + if (!instance) { + LOGE("Can't find instance. instance_id=%d", instance_id); + result.Set(v8::Undefined(isolate)); + return; + } + + auto sync_cb = [](const char* reply, const size_t /*size*/, + const bool /*binary*/, + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::ReturnValue result(args.GetReturnValue()); + result.Set(v8::String::NewFromUtf8(isolate, reply)); + }; + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + instance->set_send_sync_message_listener( + std::bind(sync_cb, _1, _2, _3, args)); + v8::String::Utf8Value msg(args[1]->ToString()); + instance->HandleSyncMessage(std::string(*msg)); +} + +// static +void NativeBinding::SetMessageListener( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 2 || !args[0]->IsNumber()) { + result.Set(v8::Undefined(isolate)); + return; + } + + int instance_id = args[0]->Int32Value(); + g_listeners[instance_id].Reset( + isolate, v8::Handle::Cast(args[1])); +} + +// static +void NativeBinding::SetRuntimeVariable( + const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::HandleScope handle_scope(isolate); + v8::ReturnValue result(args.GetReturnValue()); + + if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { + result.Set(v8::Undefined(isolate)); + return; + } + + v8::String::Utf8Value key(args[0]->ToString()); + v8::String::Utf8Value value(args[1]->ToString()); + + g_runtime_variables[std::string(*key)] = std::string(*value); + + result.Set(v8::Boolean::New(isolate, true)); +} + +// static +// TODO(WonyoungChoi): Make this function thread-safety. +void NativeBinding::PostMessageToJSCallback( + const char* msg, const size_t size, const bool binary, int instance_id) { + auto listener = g_listeners.find(instance_id); + if (listener == g_listeners.end()) { + LOGW("Can't find callback. instance_id=%d", instance_id); + return; + } + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + v8::Local args[1]; + + if (binary) { + args[0] = v8::ArrayBuffer::New(isolate, (void*)(msg), size); + } else { + args[0] = v8::String::NewFromUtf8(isolate, msg); + } + + v8::Local func = + v8::Local::New(isolate, listener->second); + func->Call(v8::Null(isolate), 1, args); +} + +} // namespace extensions +} // namespace xwalk + +extern "C" { + static void NodeInit(v8::Handle target) { + xwalk::extensions::NativeBinding::Init(target); + } + NODE_MODULE(native, NodeInit); +} diff --git a/node-xwalk/src/native_binding.h b/node-xwalk/src/native_binding.h new file mode 100644 index 0000000..66f9899 --- /dev/null +++ b/node-xwalk/src/native_binding.h @@ -0,0 +1,42 @@ +// Copyright (c) 2015 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 NODE_XWALK_EXTENSIONS_NATIVE_BINDING_H_ +#define NODE_XWALK_EXTENSIONS_NATIVE_BINDING_H_ + +#include +#include +#include + +namespace xwalk { +namespace extensions { + +class NativeBinding { + public: + static void Init(v8::Handle target); + + static void GetExtensionInfo( + const v8::FunctionCallbackInfo& args); + static void CreateInstance( + const v8::FunctionCallbackInfo& args); + static void DestroyInstance( + const v8::FunctionCallbackInfo& args); + static void PostMessage( + const v8::FunctionCallbackInfo& args); + static void SendSyncMessage( + const v8::FunctionCallbackInfo& args); + static void SetMessageListener( + const v8::FunctionCallbackInfo& args); + static void SetRuntimeVariable( + const v8::FunctionCallbackInfo& args); + + static void PostMessageToJSCallback( + const char* msg, const size_t size, const bool binary, + int instance_id); +}; + +} // namespace extensions +} // namespace xwalk + +#endif // NODE_XWALK_EXTENSIONS_NATIVE_BINDING_H_ diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension.h new file mode 100644 index 0000000..174915a --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension.h @@ -0,0 +1,185 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ + +// Crosswalk Extensions are modules of code loaded by Crosswalk runtime that +// allow extending its capabilities. The extension is expected to define a +// XW_Initialize() function as declared below, get the interfaces it need to +// use and register to whatever callbacks it needs, then return XW_OK. +// +// The Extension is represented by the type XW_Extension. Each extension +// loaded may be used multiple times for different pages, so to each execution +// there will be an associated XW_Instance. A reasonable analogy is that the +// XW_Extension represent a "class", and have concrete instances running. +// +// An interface is a struct with a set of functions, provided by Crosswalk, +// that allow the extension code to interact with the web content. Certain +// functions in an interface are used to register callbacks, so that Crosswalk +// can call the extension at specific situations. +// +// Crosswalk won't call an extension's XW_Initialize() multiple times in the +// same process. + +#ifdef __cplusplus +extern "C" { +#endif + +#if __GNUC__ >= 4 +#define XW_EXPORT __attribute__ ((visibility("default"))) +#elif defined(_MSC_VER) +#define XW_EXPORT __declspec(dllexport) +#endif + +#include + + +// XW_Extension is used to identify your extension when calling functions from +// the API. You should always use the XW_Extension received at XW_Initialize(). +// +// XW_Instance is used to identify different web contents using your +// extension. Each time a new web content is created you can be notified +// registering the XW_CreatedInstanceCallback, that receives the new +// XW_Instance. When interacting with an Instance (for example to post a +// message), you should pass the corresponding XW_Instance. +// +// In both types the zero value is never used by Crosswalk, so can be used to +// initialize variables. +typedef int32_t XW_Extension; +typedef int32_t XW_Instance; + +enum { + XW_OK = 0, + XW_ERROR = -1 +}; + +// Returns a struct containing functions to be used by the extension. Those +// structs can be stored statically and used until the extension is unloaded. +// Extensions should use definitions like XW_CORE_INTERFACE, instead of using +// the versioned definition or the literal string. Returns NULL if the +// interface is not supported. +typedef const void* (*XW_GetInterface)(const char* interface_name); + + +typedef int32_t (*XW_Initialize_Func)(XW_Extension extension, + XW_GetInterface get_interface); + +// XW_Initialize is called after the extension code is loaded. The 'extension' +// value should be used in further calls that expect XW_Extension argument. +// +// The 'get_interface' function should be used to get access to functions that +// interact with the web content. It is only valid during the execution of the +// XW_Initialize() function. +// +// This function should return XW_OK when the extension was succesfully +// loaded, otherwise XW_ERROR. +XW_EXPORT int32_t XW_Initialize(XW_Extension extension, + XW_GetInterface get_interface); + + +// +// XW_CORE_INTERFACE: Basic functionality for Crosswalk Extensions. All +// extensions should use this interface to set at least their name. +// + +#define XW_CORE_INTERFACE_1 "XW_CoreInterface_1" +#define XW_CORE_INTERFACE XW_CORE_INTERFACE_1 + +typedef void (*XW_CreatedInstanceCallback)(XW_Instance instance); +typedef void (*XW_DestroyedInstanceCallback)(XW_Instance instance); +typedef void (*XW_ShutdownCallback)(XW_Extension extension); + +struct XW_CoreInterface_1 { + // Set the name of the extension. It is used as the namespace for the + // JavaScript code exposed by the extension. So extension named + // 'my_extension', will expose its JavaScript functionality inside + // the 'my_extension' namespace. + // + // This function should be called only during XW_Initialize(). + void (*SetExtensionName)(XW_Extension extension, const char* name); + + // Set the JavaScript code loaded in the web content when the extension is + // used. This can be used together with the messaging mechanism to implement + // a higher-level API that posts messages to extensions, see + // XW_MESSAGING_INTERFACE below. + // + // The code will be executed inside a JS function context with the following + // objects available: + // + // - exports: this object should be filled with properties and functions + // that will be exposed in the namespace associated with this + // extension. + // + // - extension.postMessage(): post a string message to the extension native + // code. See below for details. + // - extension.setMessageListener(): allow setting a callback that is called + // when the native code sends a message + // to JavaScript. Callback takes a string. + // + // This function should be called only during XW_Initialize(). + void (*SetJavaScriptAPI)(XW_Extension extension, const char* api); + + // Register callbacks that are called when an instance of this extension + // is created or destroyed. Everytime a new web content is loaded, it will + // get a new associated instance. + // + // This function should be called only during XW_Initialize(). + void (*RegisterInstanceCallbacks)(XW_Extension extension, + XW_CreatedInstanceCallback created, + XW_DestroyedInstanceCallback destroyed); + + // Register a callback to be executed when the extension will be unloaded. + // + // This function should be called only during XW_Initialize(). + void (*RegisterShutdownCallback)(XW_Extension extension, + XW_ShutdownCallback shutdown_callback); + + // These two functions are conveniences used to associated arbitrary data + // with a given XW_Instance. They can be used only with instances that were + // created but not yet completely destroyed. GetInstanceData() can be used + // during the destroyed instance callback. If not instance data was set, + // getting it returns NULL. + void (*SetInstanceData)(XW_Instance instance, void* data); + void* (*GetInstanceData)(XW_Instance instance); +}; + +typedef struct XW_CoreInterface_1 XW_CoreInterface; + + +// +// XW_MESSAGING_INTERFACE: Exchange asynchronous messages with JavaScript +// code provided by extension. +// + +#define XW_MESSAGING_INTERFACE_1 "XW_MessagingInterface_1" +#define XW_MESSAGING_INTERFACE XW_MESSAGING_INTERFACE_1 + +typedef void (*XW_HandleMessageCallback)(XW_Instance instance, + const char* message); + +struct XW_MessagingInterface_1 { + // Register a callback to be called when the JavaScript code associated + // with the extension posts a message. Note that the callback will be called + // with the XW_Instance that posted the message as well as the message + // contents. + void (*Register)(XW_Extension extension, + XW_HandleMessageCallback handle_message); + + // Post a message to the web content associated with the instance. To + // receive this message the extension's JavaScript code should set a + // listener using extension.setMessageListener() function. + // + // This function is thread-safe and can be called until the instance is + // destroyed. + void (*PostMessage)(XW_Instance instance, const char* message); +}; + +typedef struct XW_MessagingInterface_1 XW_MessagingInterface; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension_EntryPoints.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension_EntryPoints.h new file mode 100644 index 0000000..54532a9 --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension_EntryPoints.h @@ -0,0 +1,49 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_ENTRYPOINTS_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_ENTRYPOINTS_H_ + +// NOTE: This file and interfaces marked as internal are not considered stable +// and can be modified in incompatible ways between Crosswalk versions. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#error "You should include XW_Extension.h before this file" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define XW_INTERNAL_ENTRY_POINTS_INTERFACE_1 \ + "XW_Internal_EntryPointsInterface_1" +#define XW_INTERNAL_ENTRY_POINTS_INTERFACE \ + XW_INTERNAL_ENTRY_POINTS_INTERFACE_1 + +// +// XW_INTERNAL_ENTRY_POINTS_INTERFACE: provides a way for extensions to add +// more information about its implementation. For now, allow extensions to +// specify more objects that the access should cause the extension to be +// loaded. +// + +struct XW_Internal_EntryPointsInterface_1 { + // Register extra entry points for this extension. An "extra" entry points + // are objects outside the implicit namespace for which the extension should + // be loaded when they are touched. + // + // This function should be called only during XW_Initialize(). + void (*SetExtraJSEntryPoints)(XW_Extension extension, + const char** entry_points); +}; + +typedef struct XW_Internal_EntryPointsInterface_1 + XW_Internal_EntryPointsInterface; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_ENTRYPOINTS_H_ + diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension_Message_2.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Message_2.h new file mode 100644 index 0000000..f417f88 --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Message_2.h @@ -0,0 +1,64 @@ +// Copyright (c) 2015 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_MESSAGE_2_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_MESSAGE_2_H_ + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#error "You should include XW_Extension.h before this file" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define XW_MESSAGING_INTERFACE_2 "XW_MessagingInterface_2" + +typedef void (*XW_HandleBinaryMessageCallback)(XW_Instance instance, + const char* message, + const size_t size); + +struct XW_MessagingInterface_2 { + // Register a callback to be called when the JavaScript code associated + // with the extension posts a message. Note that the callback will be called + // with the XW_Instance that posted the message as well as the message + // contents. + void (*Register)(XW_Extension extension, + XW_HandleMessageCallback handle_message); + + // Post a message to the web content associated with the instance. To + // receive this message the extension's JavaScript code should set a + // listener using extension.setMessageListener() function. + // + // This function is thread-safe and can be called until the instance is + // destroyed. + void (*PostMessage)(XW_Instance instance, const char* message); + + // Register a callback to be called when the JavaScript code associated + // with the extension posts a binary message (ArrayBuffer object). + // Note that the callback will be called with the XW_Instance that posted + // the message as well as the message contents. + void (*RegisterBinaryMesssageCallback)( + XW_Extension extension, + XW_HandleBinaryMessageCallback handle_message); + + // Post a binary message to the web content associated with the instance. To + // receive this message the extension's JavaScript code should set a + // listener using extension.setMessageListener() function. + // The JavaScript message listener function would receive the binary message + // in an ArrayBuffer object. + // + // This function is thread-safe and can be called until the instance is + // destroyed. + void (*PostBinaryMessage)(XW_Instance instance, + const char* message, size_t size); +}; + +typedef struct XW_MessagingInterface_2 XW_MessagingInterface2; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_MESSAGE_2_H_ diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension_Permissions.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Permissions.h new file mode 100644 index 0000000..d25484e --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Permissions.h @@ -0,0 +1,41 @@ +// Copyright (c) 2014 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_PERMISSIONS_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_PERMISSIONS_H_ + +// NOTE: This file and interfaces marked as internal are not considered stable +// and can be modified in incompatible ways between Crosswalk versions. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#error "You should include XW_Extension.h before this file" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define XW_INTERNAL_PERMISSIONS_INTERFACE_1 \ + "XW_Internal_PermissionsInterface_1" +#define XW_INTERNAL_PERMISSIONS_INTERFACE \ + XW_INTERNAL_PERMISSIONS_INTERFACE_1 + +// +// XW_INTERNAL_PERMISSIONS_INTERFACE: provides a way for extensions +// check if they have the proper permissions for certain APIs. +// + +struct XW_Internal_PermissionsInterface_1 { + int (*CheckAPIAccessControl)(XW_Extension extension, const char* api_name); + int (*RegisterPermissions)(XW_Extension extension, const char* perm_table); +}; + +typedef struct XW_Internal_PermissionsInterface_1 + XW_Internal_PermissionsInterface; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_PERMISSIONS_H_ diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension_Runtime.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Runtime.h new file mode 100644 index 0000000..11ad307 --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension_Runtime.h @@ -0,0 +1,44 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_RUNTIME_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_RUNTIME_H_ + +// NOTE: This file and interfaces marked as internal are not considered stable +// and can be modified in incompatible ways between Crosswalk versions. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#error "You should include XW_Extension.h before this file" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define XW_INTERNAL_RUNTIME_INTERFACE_1 \ + "XW_Internal_RuntimeInterface_1" +#define XW_INTERNAL_RUNTIME_INTERFACE \ + XW_INTERNAL_RUNTIME_INTERFACE_1 + +// +// XW_INTERNAL_RUNTIME_INTERFACE: allow extensions to gather information +// from the runtime. +// + +struct XW_Internal_RuntimeInterface_1 { + void (*GetRuntimeVariableString)(XW_Extension extension, + const char* key, + char* value, + unsigned int value_len); +}; + +typedef struct XW_Internal_RuntimeInterface_1 + XW_Internal_RuntimeInterface; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_RUNTIME_H_ + diff --git a/node-xwalk/src/xwalk/extensions/public/XW_Extension_SyncMessage.h b/node-xwalk/src/xwalk/extensions/public/XW_Extension_SyncMessage.h new file mode 100644 index 0000000..4eddbf9 --- /dev/null +++ b/node-xwalk/src/xwalk/extensions/public/XW_Extension_SyncMessage.h @@ -0,0 +1,48 @@ +// Copyright (c) 2013 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_SYNCMESSAGE_H_ +#define XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_SYNCMESSAGE_H_ + +// NOTE: This file and interfaces marked as internal are not considered stable +// and can be modified in incompatible ways between Crosswalk versions. + +#ifndef XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_H_ +#error "You should include XW_Extension.h before this file" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// +// XW_INTERNAL_SYNC_MESSAGING_INTERFACE: allow JavaScript code to send a +// synchronous message to extension code and block until response is +// available. The response is made available by calling the SetSyncReply +// function, that can be done from outside the context of the SyncMessage +// handler. +// + +#define XW_INTERNAL_SYNC_MESSAGING_INTERFACE_1 \ + "XW_InternalSyncMessagingInterface_1" +#define XW_INTERNAL_SYNC_MESSAGING_INTERFACE \ + XW_INTERNAL_SYNC_MESSAGING_INTERFACE_1 + +typedef void (*XW_HandleSyncMessageCallback)(XW_Instance instance, + const char* message); + +struct XW_Internal_SyncMessagingInterface_1 { + void (*Register)(XW_Extension extension, + XW_HandleSyncMessageCallback handle_sync_message); + void (*SetSyncReply)(XW_Instance instance, const char* reply); +}; + +typedef struct XW_Internal_SyncMessagingInterface_1 + XW_Internal_SyncMessagingInterface; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // XWALK_EXTENSIONS_PUBLIC_XW_EXTENSION_SYNCMESSAGE_H_ diff --git a/packaging/jsnative.manifest b/packaging/jsnative.manifest new file mode 100644 index 0000000..75b0fa5 --- /dev/null +++ b/packaging/jsnative.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/jsnative.spec b/packaging/jsnative.spec new file mode 100644 index 0000000..9ad276b --- /dev/null +++ b/packaging/jsnative.spec @@ -0,0 +1,54 @@ +%define cmake_tmp cmake_build_tmp + +Name: jsnative +Summary: Node.js modules for JSNative apps +Version: 0.9.0 +Release: 1 +Group: Development/Libraries +License: Apache-2.0 and BSD-3-Clause +URL: https://www.tizen.org +Source0: %{name}-%{version}.tar.gz +Source1: %{name}.manifest + +BuildRequires: cmake +BuildRequires: pkgconfig(appcore-efl) +BuildRequires: pkgconfig(aul) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(nodejs) +Requires: nodejs + +%description +Node.js modules for JSNative apps + +%prep +%setup -q +cp %{SOURCE1} . + +%build + +# node-xwalk +mkdir -p cmake_build_tmp +cd cmake_build_tmp +cmake .. \ + -DCMAKE_INSTALL_PREFIX=%{_prefix} + +make %{?jobs:-j%jobs} + + +%install +rm -rf %{buildroot} + +mkdir -p %{buildroot}%{_datadir}/license +cp LICENSE %{buildroot}%{_datadir}/license/%{name} +cat LICENSE.BSD >> %{buildroot}%{_datadir}/license/%{name} + +cd cmake_build_tmp +%make_install + +%clean +rm -rf %{buildroot} + +%files +%manifest %{name}.manifest +%{_datadir}/license/%{name} +%{_prefix}/lib/node/*