Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,243 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var Promise = require('promise');
var debug = require('debug')('host/host');
var request = require('./lib/request');
var spawn = require('child_process').spawn;
var uuid = require('uuid');
var _ = require('lodash');
var VENV = 'VIRTUALENV_PATH' in process.env ?
process.env.VIRTUALENV_PATH :
__dirname + '/../venv';
var _FORCE_DEBUG = false;
function Host(socketPath, process, log) {
debug('Created new host', socketPath);
this.socketPath = socketPath;
this.process = process;
this.log = log;
this.sessions = {};
this.pendingSessions = [];
this.error = null;
this.restarting = Promise.resolve();
this.isRequestInProgress = false;
this.didCrashDuringRequest = false;
this.onerror = this.onerror.bind(this);
this.onexit = this.onexit.bind(this);
process.on('error', this.onerror);
process.on('exit', this.onexit);
var restart = this.restart.bind(this);
// XXXDebug Pro Tip: set _FORCE_DEBUG to see this output instead of piping it.
if (!_FORCE_DEBUG) {
process.stdout.on('data', function(chunk) {
var str = chunk.toString();
if (str.indexOf('Exception') !== -1) {
console.info('Will restart -- Found error in "', str, '"');
restart();
}
});
process.stderr.on('data', function(chunk) {
var str = chunk.toString();
// Bad file descriptor is a non-issue. Don't restart when this happens.
// It simply means that the file descriptor we use to pipe the logs was
// closed pre-maturely. We'll recover from this.
if (str.indexOf('error') !== -1 &&
str.indexOf('Bad file descriptor') === -1) {
console.info('Will restart -- Found error in "', str, '"');
restart();
}
});
}
EventEmitter.call(this);
}
module.exports = Host;
Host.prototype = {
__proto__: EventEmitter.prototype,
destroy: function() {
// If there are any pending session creates wait for those to cleanly finish
// first.
if (this.pendingSessions.length) {
return Promise.all(this.pendingSessions).then(this.destroy.bind(this));
}
return Promise.all(
_.map(this.sessions, function(session) {
return session.destroy();
})
)
.then(function() {
if (Object.keys(this.sessions).length !== 0) {
return Promise.reject(new Error('Not all sessions were deleted'));
}
var proc = this.process;
proc.removeListener('exit', this.onexit);
return new Promise(function(accept, reject) {
proc.once('exit', function() {
proc.removeListener('error', this.onerror);
if (this.error) return reject(this.error);
accept();
}.bind(this));
proc.kill();
}.bind(this));
}.bind(this));
},
restart: function() {
// Python died so let's restart! Yay! Long live python.
console.log('Python crashed! Will restart service...');
if (this.isRequestInProgress) {
console.log('There was a request in progress!');
this.didCrashDuringRequest = true;
}
this.restarting = new Promise(function(resolve) {
// Kill ourselves.
this.process.removeListener('error', this.onerror);
this.process.removeListener('exit', this.onexit);
if (!this.process.connected) {
return resolve();
}
this.process.on('exit', resolve);
this.process.kill();
}.bind(this))
.then(function() {
// Start a new python child.
return spawnPythonChild();
})
.then(function(details) {
this.socketPath = details.socketPath;
this.process = details.process;
this.log = details.log;
this.process.on('error', this.onerror);
this.process.on('exit', this.onexit);
}.bind(this));
},
/**
Issue a request to the hosts underlying python http server.
*/
request: function(path, options) {
if (this.error) {
return Promise.reject(this.error);
}
return this.restarting.then(function() {
this.isRequestInProgress = true;
return request(this.socketPath, path, options);
}.bind(this))
.catch(function() {}) // TODO(gaye): Replace with finally
.then(function(result) {
this.isRequestInProgress = false;
if (this.didCrashDuringRequest) {
console.log('Will retry ' + path + ' request...');
this.didCrashDuringRequest = false;
return this.request(path, options);
}
return result;
}.bind(this));
},
onerror: function(error) {
debug('Python process error', error.toString());
this.error = error;
this.process.removeListener('exit', this.onexit);
this.process.kill();
},
onexit: function(exit) {
debug('Python process exited unexpectedly', JSON.stringify(exit));
this.error = new Error('Python unexpected exit ' + JSON.stringify(exit));
this.process.removeListener('exit', this.onexit);
}
};
Host.create = function() {
return spawnPythonChild().then(function(details) {
return new Host(details.socketPath, details.process, details.log);
});
};
function spawnPythonChild() {
var socketPath = '/tmp/marionette-socket-host-' + uuid.v1() + '.sock';
// XXXDebug Pro Tip: set stdio to [0, 1, 2] to get Python output.
var pythonChild = spawnVirtualEnv(
'gaia-integration',
['--path=' + socketPath],
{ stdio: _FORCE_DEBUG === false ?
['pipe', 'pipe', 'pipe', 'pipe'] :
[0, 1, 2] }
);
var failOnChildError = new Promise(function(accept, reject) {
function onError(error) {
debug('Python process error during initial connect', error.toString());
reject(error);
}
pythonChild.once('error', onError);
pythonChild.once('exit', function(exit) {
// Ensure we don't call error callback somehow...
pythonChild.removeListener('error', onError);
reject(new Error(
'Unexpected exit during connect: ' +
'signal = ' + exit.signal + ', ' +
'code = ' + exit.code
));
});
});
var connect = request(socketPath, '/connect').then(function() {
pythonChild.removeAllListeners('error');
pythonChild.removeAllListeners('exit');
return {
socketPath: socketPath,
process: pythonChild,
log: pythonChild.stdio[3]
};
});
return Promise.race([connect, failOnChildError]);
}
/**
Wrapper for spawning a python process which expects a venv with particular
packages. Same interface as spawn but overrides path and ensures certain env
variables are not set which conflict.
@param {String} bin path to binary to execute.
@param {Array} argv list of arguments.
@param {Object} opts options for node's spawn.
@return {ChildProcess}
*/
function spawnVirtualEnv(bin, argv, opts) {
opts = opts || {};
// Clone current environment variables...
var env = {};
_.assign(env, process.env);
opts.env = env;
// Prepend binary wrappers to path.
env.PATH = VENV + '/bin/:' + process.env.PATH;
// Ensure we don't conflict with other wrappers or package managers.
delete env.PYTHONHOME;
return spawn(bin, argv, opts);
}