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,217 @@
'use strict';
var utils = window.utils || {};
if (!utils.sdcard) {
var SdCard = utils.sdcard = {
NOT_INITIALIZED: 0,
NOT_AVAILABLE: 1,
AVAILABLE: 2,
SHARED: 3,
observers: {}
};
SdCard.status = SdCard.NOT_INITIALIZED;
SdCard.deviceStorages = (navigator.getDeviceStorages ? navigator.getDeviceStorages('sdcard') : []);
SdCard.deviceStorage = Array.isArray(SdCard.deviceStorages) &&
SdCard.deviceStorages[0];
SdCard._toStatus = function toStatus(state) {
switch (state) {
case 'available':
SdCard.status = SdCard.AVAILABLE;
break;
// NOTE: The 'SHARED' state is equivalent to NOT_AVAILABLE, since we
// could have inconsistencies if we allow changing the sdcard content
// meanwhile exporting/importing.
// It's needed to have it's own state solely for showing the proper
// message when needed
case 'shared':
SdCard.status = SdCard.SHARED;
break;
case 'unavailable':
case 'deleted':
SdCard.status = SdCard.NOT_AVAILABLE;
break;
}
};
SdCard.updateStorageState = function sd_updateStorageState(state) {
SdCard._toStatus(state);
Object.keys(this.observers).forEach(function onObserver(name) {
if (typeof(SdCard.observers[name]) === 'function') {
SdCard.observers[name].call(null, state);
}
});
};
if (SdCard.deviceStorage) {
SdCard.deviceStorage
.addEventListener('change', function sd_deviceStorageChangeHandler(e) {
SdCard.updateStorageState(e.reason);
});
if (SdCard.status === SdCard.NOT_INITIALIZED) {
SdCard.deviceStorage.available().onsuccess = (function(e) {
SdCard.updateStorageState(e.target.result);
});
}
}
/**
* Check whether there is a SD card inserted in the device.
* @return {Boolean} true if sdcard available, false otherwise.
*/
SdCard.checkStorageCard = function sd_checkStorageCard() {
return SdCard.status === SdCard.AVAILABLE;
};
SdCard.getStatus = function(cb) {
if (!SdCard.deviceStorage) {
SdCard._toStatus('unavailable');
cb(SdCard.status);
return;
}
var req = SdCard.deviceStorage.available();
req.onsuccess = function() {
SdCard._toStatus(req.result);
cb(SdCard.status);
};
req.onerror = function() {
console.error('Error while determining SD Status', req.error.name);
cb(SdCard.status);
};
};
/**
* Retrieves files that adjust to the kind specified in the
* parameters from the SD card inserted in the device.
*
* @param {String[]} mimes Possible mime types of the files to be found.
* @param {String[]} exts Possible file extensions of the files to be found.
* @param {Function} cb Callback.
*/
SdCard.retrieveFiles = function retrieveFilesContent(mimes, exts, cb) {
var fileArray = [];
var forbiddenDirectories = ['.Trash'];
// getDeviceStorages('sdcard') can return more than one storage unit
// in different order, depends on the default media storage setting,
// so we need to iterate though them to be sure we checked the proper
// SD Card
var storages = (navigator.getDeviceStorages ? navigator.getDeviceStorages('sdcard') : []);
var numberOfStorages = storages.length;
var currentStorage = 0;
var cursorOnError = function() {
cb(this.error.name);
};
// RegExp that checks if a string contains any of the extensions.
var reExt = new RegExp('.(' + exts.join('|') + ')$');
var cursorOnSuccess = function(e) {
var file = e.target.result;
// If we don't have any more files on the storage...
if (!file) {
// ... but we have more storages...
if (++currentStorage < numberOfStorages) {
// ... check the next one
cursor = storages[currentStorage].enumerate();
cursor.onsuccess = cursorOnSuccess;
cursor.onerror = cursorOnError;
} else {
cb(null, fileArray);
}
} else {
if ((mimes.indexOf(file.type) === -1) && !reExt.test(file.name)) {
cursor.continue();
} else {
if (forbiddenDirectories.indexOf(file.name) === -1) {
fileArray.push(file);
cursor.continue();
}
}
}
};
var cursor = storages[currentStorage].enumerate();
cursor.onsuccess = cursorOnSuccess;
cursor.onerror = cursorOnError;
};
/**
* Incrementally extracts vcard text from a file.
*
* @param {Object} file File Object.
* @param {Function} cb Function to call for each chunk of vcards retrieved
* from the file.
*/
SdCard.getTextFromFile = function(file, cb) {
if (!file) {
return Promise.reject();
}
return new Promise((resolve, reject) => {
const END_VCARD = 'end:vcard';
const CHUNK_SIZE = 1 * 1024 * 1024; // 1Mb
var fileReader = new FileReader();
var result = 0;
(function readChunk(offset, buffer) {
if (offset >= file.size) {
resolve(result);
return;
}
var chunk = file.slice(offset, offset + CHUNK_SIZE);
offset += CHUNK_SIZE;
fileReader.readAsText(chunk);
fileReader.onload = function() {
buffer += fileReader.result;
if (!buffer.match(/end:vcard/gi)) {
readChunk(offset, buffer);
return;
}
var lastContactInChunk =
buffer.toLowerCase().lastIndexOf(END_VCARD) + END_VCARD.length;
cb(buffer.substr(0, lastContactInChunk)).then(partialResult => {
result += partialResult;
readChunk(offset, buffer.substr(lastContactInChunk + 1));
}).catch(error => {
console.warn(error);
});
};
})(0 /* initial offset */, '' /* initial empty buffer */);
});
};
// Subscribe a callback for sdcard changes. A name is needed
// to refer to this observer.
// @param {String} identifier for the observer
// @param {Function} the callback to be invoqued on a change
// @param {Boolean} if the callback exists, override it
SdCard.subscribeToChanges = function(name, func, force) {
if (this.observers[name] !== undefined && !force) {
return false;
}
this.observers[name] = func;
return true;
};
// Remove the subscription to listen to changes for the specific
// identifier
// @param {String} the identifier
SdCard.unsubscribeToChanges = function(name) {
if (this.observers[name]) {
delete this.observers[name];
return true;
} else {
return false;
}
};
}

View file

@ -0,0 +1,423 @@
'use strict';
/* global LazyLoader, TelIndexer */
// Reader Module for FB Data in a Datastore
// To use this library you need to include 'shared/js/fb/fb_request.js'
// WARNING: This file lazy loads 'shared/js/fb/fb_tel_index.js'
var fb = self.fb || {};
self.fb = fb;
(function() {
var contacts = fb.contacts || {};
fb.contacts = contacts;
var datastore;
// Datastore name declared on the manifest.webapp
var DATASTORE_NAME = 'Gaia_Facebook_Friends';
// Record Id for the index
var INDEX_ID = 1;
// Indicates the initialization state
var readyState = 'notInitialized';
// Custom event for notifying initializations
var INITIALIZE_EVENT = 'fb_ds_init';
// Last known revision Id. Used to determine whether the index has to
// be reloaded or not
var revisionId;
// Creates the internal Object in the datastore that will act as an index
function createIndex() {
return {
// By tel number and all its possible variants
// (We are not supporting dups right now)
byTel: Object.create(null),
// Prefix tree for enabling searching by partial tel numbers
treeTel: []
};
}
// The index we need to keep the correspondance between FB Friends and
// datastore Ids
var index;
// Ensures a proper error object is returned
function safeError(err) {
if (err && err.name) {
return err;
}
return {
name: 'UnknownError'
};
}
function notifyOpenSuccess(cb) {
readyState = 'initialized';
if (typeof cb === 'function') {
window.setTimeout(cb, 0);
}
var ev = new CustomEvent(INITIALIZE_EVENT);
document.dispatchEvent(ev);
}
function initError(outRequest, error) {
outRequest.failed(error);
}
function handleInitError(err, errorCb) {
readyState = 'error';
contacts.error = err;
if (typeof errorCb === 'function') {
window.setTimeout(function() {
errorCb(err);
}, 0);
}
// We resume other callers state
document.dispatchEvent(new CustomEvent(INITIALIZE_EVENT));
}
// Creates a default handler for errors
function defaultError(request) {
return defaultErrorCb.bind(null, request);
}
// Creates a default handler for success
function defaultErrorCb(request, error) {
request.failed(error);
}
function setIndex(obj) {
index = obj && obj.byTel && Array.isArray(obj.treeTel) ? obj : createIndex();
}
function successGet(outReq, data) {
outReq.done(data);
}
function errorGet(outReq, uid, err) {
window.console.error('Error while getting object for UID: ', uid);
outReq.failed(err.name);
}
Object.defineProperty(contacts, 'datastore', {
get: function getDataStore() { return datastore; },
enumerable: false,
configurable: false
});
Object.defineProperty(contacts, 'dsIndex', {
get: function getIndex() { return index; },
set: setIndex,
enumerable: false,
configurable: false
});
/**
* Allows to obtain the FB friend information by UID
*
*
*/
contacts.get = function(uid) {
var outRequest = new fb.utils.Request();
window.setTimeout(function get() {
contacts.init(function() {
doGet(uid, outRequest);
}, function(err) {
initError(outRequest, err);
});
}, 0);
return outRequest;
};
function doGet(uid, outRequest) {
var successCb = successGet.bind(null, outRequest);
var errorCb = errorGet.bind(null, outRequest, uid);
datastore.get(uid).then(successCb, errorCb);
}
/**
* Allows to get FB information by tel number (short or long)
* The tel number must be complete
*
*/
contacts.getByPhone = function(tel) {
var outRequest = new fb.utils.Request();
window.setTimeout(function get_by_phone() {
contacts.init(function get_by_phone() {
doGetByPhone(tel, outRequest);
},
function(err) {
initError(outRequest, err);
});
}, 0);
return outRequest;
};
function doGetByPhone(tel, outRequest) {
var dsId;
var normalizedNumber = navigator.mozPhoneNumberService.normalize(tel);
if (datastore.revisionId !== revisionId) {
window.console.info('Datastore revision id has changed!');
// Refreshing the index just in case
datastore.get(INDEX_ID).then(function success(obj) {
setIndex(obj);
revisionId = datastore.revisionId;
dsId = index.byTel[normalizedNumber];
if (typeof dsId !== 'undefined') {
datastore.get(dsId).then(function success(friend) {
outRequest.done(friend);
}, defaultError(outRequest));
}
else {
outRequest.done(null);
}
}, function(err) {
err = safeError(err);
window.console.error('The index cannot be refreshed: ', err.name);
outRequest.failed(err);
});
}
else {
dsId = index.byTel[normalizedNumber];
if (typeof dsId !== 'undefined') {
datastore.get(dsId).then(function success(friend) {
outRequest.done(friend);
}, defaultError(outRequest));
}
else {
outRequest.done(null);
}
}
}
/**
* Allows to search for friends by different criteria.
* Numbers can be partial and partial matchings will be found
*
* by === tel : By Tel number
*
*/
contacts.search = function(by, number) {
var outRequest = new fb.utils.Request();
window.setTimeout(function do_search() {
contacts.init(function do_search_init() {
if (by === 'phone') {
doSearchByPhone(number, outRequest);
}
},
function(err) {
initError(outRequest, err);
});
}, 0);
return outRequest;
};
function doSearchByPhone(number, outRequest) {
var normalizedNumber = navigator.mozPhoneNumberService.normalize(number);
LazyLoader.load(['/shared/js/fb/fb_tel_index.js',
'/shared/js/binary_search.js'], function() {
var toSearchNumber = normalizedNumber;
// TODO: Temporal way of searching for international numbers
// A follow-up is needed by using PhoneNumber.js exposed to Gaia
if (number.charAt(0) === '+') {
toSearchNumber = number.substring(1);
}
if (datastore.revisionId !== revisionId) {
window.console.info('Datastore revision id has changed!');
// Refreshing the index
datastore.get(INDEX_ID).then(function success(obj) {
setIndex(obj);
revisionId = datastore.revisionId;
var results = TelIndexer.search(index.treeTel, toSearchNumber);
var out = null;
if (results.length > 0) {
out = datastore.get.apply(datastore, results);
}
return out;
},function(err) {
err = safeError(err);
window.console.error('The index cannot be refreshed: ', err.name);
outRequest.failed(err);
}).then(function success(objList) {
if (objList && !Array.isArray(objList)) {
objList = [objList];
}
else {
objList = [];
}
outRequest.done(objList);
}, function error(err) {
err = safeError(err);
window.console.error('Error while retrieving result data: ',
err.name);
outRequest.failed(err);
});
}
else {
var results = TelIndexer.search(index.treeTel, toSearchNumber);
if (results.length > 0) {
datastore.get.apply(datastore, results).then(
function success(objList) {
if (!Array.isArray(objList)) {
objList = [objList];
}
outRequest.done(objList);
}, function error(err) {
err = safeError(err);
window.console.error('Error while retrieving result data: ',
err.name);
outRequest.failed(err);
});
}
else {
outRequest.done(results);
}
}
});
}
/**
* Refreshes the index data
*
*/
contacts.refresh = function() {
var outRequest = new fb.utils.Request();
window.setTimeout(function clear() {
contacts.init(function() {
doRefresh(outRequest);
},
function(err) {
initError(outRequest, err);
});
}, 0);
return outRequest;
};
function doRefresh(outRequest) {
datastore.get(INDEX_ID).then(function success(obj) {
setIndex(obj);
outRequest.done();
}, defaultError(outRequest));
}
/**
* Allows to obtain how many records contain he FB Database
*
*/
contacts.getLength = function() {
var retRequest = new fb.utils.Request();
window.setTimeout(function() {
contacts.init(function get_all() {
doGetLength(retRequest);
},
function(err) {
initError(retRequest, err);
});
}, 0);
return retRequest;
};
/**
* Allows to return the total number of records in the DataStore (minus 1)
* That's because the index object also counts
*
*/
function doGetLength(outRequest) {
datastore.getLength().then(function success(length) {
outRequest.done(length - 1);
},
function error(err) {
outRequest.failed(safeError(err));
});
}
// Needed only for testing purposes
contacts.restart = function() {
readyState = 'notInitialized';
};
/**
* Initialization function
*
*/
contacts.init = function(cb, errorCb) {
if (!navigator.getDataStores) {
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
return;
}
if (readyState === 'initialized') {
cb();
return;
}
if (readyState === 'initializing') {
document.addEventListener(INITIALIZE_EVENT, function oninitalized() {
cb();
document.removeEventListener(INITIALIZE_EVENT, oninitalized);
});
return;
}
readyState = 'initializing';
navigator.getDataStores(DATASTORE_NAME).then(function success(ds) {
if (ds.length < 1) {
handleInitError({ name: 'DatastoreNotFound' }, errorCb);
return;
}
datastore = ds[0];
// Checking the length as the index should be there
datastore.getLength().then(function(length) {
if (length === 0 && datastore.readOnly === false) {
window.console.info('Adding index as length is 0');
setIndex(createIndex());
return datastore.add(index);
}
else if (length > 0) {
return datastore.get(INDEX_ID);
}
else if (datastore.readOnly === true) {
window.console.warn('The datastore is empty and readonly');
}
return null;
}).then(function add_index_success(v) {
// Index is created in order not to cause errors
if (typeof v !== 'number') {
setIndex(v);
}
revisionId = datastore.revisionId;
notifyOpenSuccess(cb);
}, function add_index_error(err) {
err = safeError(err);
window.console.error('Error while setting the index: ', err.name);
handleInitError(err, errorCb);
});
}, function error(err) {
err = safeError(err);
window.console.error('FB: Error while opening the DataStore: ',
err.name);
handleInitError(err, errorCb);
});
};
})();