Skip to content
This repository was archived by the owner on Sep 2, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ module.exports = function (grunt) {
"src" : [
"lib/**",
"locales/**",
"node-core/**",
"appshell*.png",
"Brackets",
"Brackets-node",
"cef.pak",
"devtools_resources.pak"
],
Expand Down
15 changes: 14 additions & 1 deletion appshell.gyp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,22 @@
{
'destination': '<(PRODUCT_DIR)',
'files': [
'<@(appshell_bundle_resources_linux)',
'<@(appshell_bundle_resources_linux)'
],
},
{
# Copy node executable to the output directory
'destination': '<(PRODUCT_DIR)',
'files': ['deps/node/bin/Brackets-node'],
},
{
# Copy node server files to the output directory
# The '/' at the end of the 'files' directory is very important and magically
# causes 'xcopy' to get used instead of 'copy' for recursive copies.
# This seems to be an undocumented feature of gyp.
'destination': '<(PRODUCT_DIR)',
'files': ['appshell/node-core/'],
},
],
'sources': [
'<@(includes_linux)',
Expand Down
176 changes: 167 additions & 9 deletions appshell/appshell_node_process_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,213 @@
*
*/


#include "appshell_node_process.h"
#include "appshell_node_process_internal.h"

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>


#ifndef OS_LINUX
#define OS_LINUX 1
#endif
#include "config.h"

#define BRACKETS_NODE_BUFFER_SIZE 4096
#define MAX_PATH 128

// init mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// write & read references to subprocess
FILE* streamTo;
FILE* streamFrom;

// Threads should hold mutex before using these
static int nodeState = BRACKETS_NODE_NOT_YET_STARTED;
static int nodeStartTime = 0;

// Forward declarations
//DWORD WINAPI NodeThread(LPVOID);
void* nodeThread(void*);
void* nodeReadThread(void*);
void restartNode(bool);

// Creates the thread that starts Node and then monitors the state
// of the node process.
void startNodeProcess() {

pthread_t thread_id;
if (pthread_create(&thread_id, NULL, &nodeThread, NULL) != 0)
nodeState = BRACKETS_NODE_FAILED;
}


// Thread function for the thread that starts the node process
// and monitors that process's state
void* nodeThread(void* unused) {

// get mutex
if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for Node subprocess start: %s\n",
strerror(errno));
return NULL;
}

// TODO nodeStartTime = get time();

char executablePath[MAX_PATH];
char bracketsDirPath[MAX_PATH];
char nodeExecutablePath[MAX_PATH];
char nodecorePath[MAX_PATH];

// get path to Brackets
if (readlink("/proc/self/exe", executablePath, MAX_PATH) == -1) {
fprintf(stderr, "cannot find Brackets path: %s\n", strerror(errno));
pthread_mutex_unlock(&mutex);
return NULL;
}

// strip off trailing executable name
char* lastIndexOf = strrchr(executablePath, '/');
memcpy(bracketsDirPath, executablePath, lastIndexOf - executablePath + 1);

// create node exec and node-core paths
strcpy(nodeExecutablePath, bracketsDirPath);
strcat(nodeExecutablePath, NODE_EXECUTABLE_PATH);
strcpy(nodecorePath, bracketsDirPath);
strcat(nodecorePath, NODE_CORE_PATH);

// create pipes for node process stdin/stdout
int toNode[2];
int fromNode[2];

pipe(toNode);
pipe(fromNode);

// create the Node process
pid_t child_pid = fork();
if (child_pid == 0) { // child (node) process
// close our copy of write end and
// connect read end to stdin
close(toNode[1]);
dup2(toNode[0], STDIN_FILENO);

// close our copy of read end and
// connect write end to stdout
close(fromNode[0]);
dup2(fromNode[1], STDOUT_FILENO);

// run node executable
char* arg_list[] = { nodeExecutablePath, nodecorePath, NULL};
execvp(arg_list[0], arg_list);

fprintf(stderr, "the Node process failed to start: %s\n", strerror(errno));
abort();
}
else { // parent

// close our reference of toNode's read end
// and fromNode's write end
close(toNode[0]);
close(fromNode[1]);

// convert subprocess write & read to FILE objects
streamTo = fdopen(toNode[1], "w");
streamFrom = fdopen(fromNode[0], "r");

nodeState = BRACKETS_NODE_PORT_NOT_YET_SET;

// done launching process so release mutex
if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for Node subprocess startup: %s\n",
strerror(errno));
}

// start pipe read thread
pthread_t readthread_id;
if (pthread_create(&readthread_id, NULL, &nodeReadThread, NULL) != 0)
nodeState = BRACKETS_NODE_FAILED;
// ugly - need to think more about what to do if read thread fails

}

return NULL;
}


// Thread function for the thread that reads from the Node pipe
// Reads on anonymous pipes are always blocking (OVERLAPPED reads
// are not possible) So, we need to do this in a separate thread
//

// TODO: This code first reads to a character buffer, and then
// copies it to a std::string. The code could be optimized to avoid
// this double-copy
//DWORD WINAPI NodeReadThread(LPVOID lpParam) {
//}
void* nodeReadThread(void* unused) {

char charBuf[BRACKETS_NODE_BUFFER_SIZE];
std::string strBuf("");
while (fgets(charBuf, BRACKETS_NODE_BUFFER_SIZE, streamFrom) != NULL) {
strBuf.assign(charBuf);
processIncomingData(strBuf);
}
}

// Thread function for the thread that starts the node process
// and monitors that process's state
//DWORD WINAPI NodeThread(LPVOID lpParam) {
//}

// Determines whether the current node process has run long
// enough that a restart is warranted, and initiates the startup
// if so. Can also optionally terminate the running process.
void restartNode(bool terminateCurrentProcess) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a TODO here and add a task to the user story.

}

// Sends data to the node process. If the write fails completely,
// calls restartNode.
void sendData(const std::string &data) {

if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for write to Node subprocess: %s\n",
strerror(errno));
return;
}

// write to pipe
fprintf(streamTo, "%s", data.c_str());

if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for write to Node subprocess: %s\n",
strerror(errno));
}
}

// Thread-safe way to access nodeState variable
// Returns nodeState variable
int getNodeState() {

return nodeState;
}

// Thread-safe way to set nodeState variable
void setNodeState(int newState) {

if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for setting Node state: %s\n",
strerror(errno));
return;
}
nodeState = newState;
if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for Node set state: %s\n",
strerror(errno));
}
}
12 changes: 8 additions & 4 deletions appshell/cefclient_gtk.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

Expand All @@ -13,6 +13,7 @@
#include "include/cef_frame.h"
#include "include/cef_runnable.h"
#include "client_handler.h"
#include "appshell_node_process.h"

static std::string APPICONS[] = {"appshell32.png","appshell48.png","appshell128.png","appshell256.png"};
char szWorkingDir[512]; // The current working directory
Expand All @@ -24,7 +25,7 @@ bool isReallyClosing = false;
// The global ClientHandler reference.
extern CefRefPtr<ClientHandler> g_handler;

//Application startup time
// Application startup time
time_t g_appStartupTime;

void destroy(void) {
Expand Down Expand Up @@ -75,7 +76,7 @@ int GetInitialUrl() {

if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
szInitialUrl.append(gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)));
szInitialUrl = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
gtk_widget_destroy (dialog);
return 0;
}
Expand Down Expand Up @@ -247,6 +248,9 @@ int main(int argc, char* argv[]) {
// Install an signal handler so we clean up after ourselves.
signal(SIGINT, TerminationSignalHandler);
signal(SIGTERM, TerminationSignalHandler);

// Start the node server process
startNodeProcess();

CefRunMessageLoop();

Expand All @@ -258,4 +262,4 @@ int main(int argc, char* argv[]) {
CefString AppGetProductVersionString() {
// TODO
return CefString("");
}
}
11 changes: 11 additions & 0 deletions appshell/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
#define NODE_EXECUTABLE_PATH @"/Contents/MacOS/Brackets-node"
#define NODE_CORE_PATH @"/Contents/node-core"

#endif
#ifdef OS_LINUX
// TODO linux preferences
//#define GROUP_NAME @""
//#define APP_NAME @"Brackets"
//#define WINDOW_TITLE APP_NAME
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These defines are presently unused but will likely be used by GTK calls in the same way they are used on Windows & OS X. So in the meantime, I have simply commented them out.


// Path for node resources is in dependencies dir and relative to the location of the appshell executable
#define NODE_EXECUTABLE_PATH "Brackets-node"
#define NODE_CORE_PATH "node-core"

#endif

#define REMOTE_DEBUGGING_PORT 9234
Expand Down
3 changes: 2 additions & 1 deletion installer/linux/build_installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

# grunt-contrib-copy doesn't preserve permissions
# https://github.com/gruntjs/grunt/issues/615
chmod 755 debian/package-root/opt/brackets/Brackets
chmod 755 debian/package-root/opt/brackets/brackets
chmod 755 debian/package-root/opt/brackets/Brackets
chmod 755 debian/package-root/opt/brackets/Brackets-node
chmod 755 debian/package-root/DEBIAN/prerm
chmod 755 debian/package-root/DEBIAN/postrm
chmod 755 debian/package-root/DEBIAN/postinst
Expand Down
14 changes: 7 additions & 7 deletions tasks/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ module.exports = function (grunt) {
/* use promises instead of callbacks */
link,
rename = common.rename,
exec = common.exec;
exec = common.exec,
platform = common.platform();

// cross-platform symbolic link
link = (function () {
Expand All @@ -67,7 +68,7 @@ module.exports = function (grunt) {

// task: cef
grunt.registerTask("cef", "Download and setup CEF", function () {
var config = "cef-" + common.platform() + common.arch(),
var config = "cef-" + platform + common.arch(),
zipSrc = grunt.config("curl-dir." + config + ".src"),
zipName = zipSrc.substr(zipSrc.lastIndexOf("/") + 1),
zipDest = grunt.config("curl-dir." + config + ".dest") + zipName,
Expand Down Expand Up @@ -145,7 +146,7 @@ module.exports = function (grunt) {
// rename version stamped name to cef
return rename("deps/" + zipName, "deps/cef");
}).then(function () {
if (common.platform() === "mac") {
if (platform === "mac") {
// FIXME figure out how to use fs.chmod to only do additive mode u+x
return exec("chmod u+x deps/cef/tools/*");
}
Expand Down Expand Up @@ -190,8 +191,7 @@ module.exports = function (grunt) {

// task: node-download
grunt.registerTask("node", "Download Node.js binaries and setup dependencies", function () {
var platform = common.platform(),
config = "node-" + platform + common.arch(),
var config = "node-" + platform + common.arch(),
nodeSrc = grunt.config("curl-dir." + config + ".src"),
nodeDest = [],
dest = grunt.config("curl-dir." + config + ".dest"),
Expand Down Expand Up @@ -308,7 +308,7 @@ module.exports = function (grunt) {
gypCommand;

// TODO why doesn't gyp (in the repository) work for linux?
if (common.platform() === "linux") {
if (platform === "linux") {
gypCommand = "gyp --depth .";
} else {
gypCommand = "bash -c 'gyp/gyp appshell.gyp -I common.gypi --depth=.'";
Expand All @@ -323,7 +323,7 @@ module.exports = function (grunt) {
// Run gyp
promise = exec(gypCommand);

if (common.platform() === "mac") {
if (platform === "mac") {
promise = promise.then(function () {
// FIXME port to JavaScript?
return exec("bash scripts/fix-xcode.sh");
Expand Down