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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
var AccessibilityHelper = {
|
||||
/**
|
||||
* For a set of tab elements, set aria-selected attribute in accordance with
|
||||
* the current selection.
|
||||
* @param {Object} selectedTab a tab to select object.
|
||||
* @param {Array} tabs an array of tabs.
|
||||
*/
|
||||
setAriaSelected: function ah_setAriaSelected(selectedTab, tabs) {
|
||||
// In case tabs is a NodeList, that does not have forEach.
|
||||
Array.prototype.forEach.call(tabs, function setAriaSelectedAttr(tab) {
|
||||
tab.setAttribute('aria-selected',
|
||||
tab === selectedTab ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.AccessibilityHelper = AccessibilityHelper;
|
||||
|
||||
})(window);
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
/* exported BluetoothHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
var BluetoothHelper = function() {
|
||||
var profiles = {
|
||||
'HFP': 0x111E,
|
||||
'A2DP': 0x110D
|
||||
};
|
||||
|
||||
var _bluetooth = window.navigator.mozBluetooth;
|
||||
var _isReady = false;
|
||||
var _callbacks = [];
|
||||
|
||||
var _adapter = null;
|
||||
var _oldAdapter = null; // The varaible is accessed for v1 only.
|
||||
var _v2 = true;
|
||||
|
||||
var _ready = function(callback) {
|
||||
if (!callback || !_bluetooth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isReady) {
|
||||
callback();
|
||||
} else {
|
||||
_callbacks.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
var _handleRequest = function(request, callback, errorcb) {
|
||||
request.onsuccess = function() {
|
||||
if (callback) {
|
||||
callback(request.result);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
console.log('Error handling bluetooth request');
|
||||
if (errorcb) {
|
||||
errorcb();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// run callbacks when adapter is ready
|
||||
var _processCallbacks = function() {
|
||||
if (_adapter) {
|
||||
_isReady = true;
|
||||
|
||||
_callbacks.forEach(function(callback) {
|
||||
callback();
|
||||
});
|
||||
// clean up the _callback queue
|
||||
_callbacks = [];
|
||||
} else {
|
||||
// We can do nothing without default adapter.
|
||||
console.log('BluetoothHelper(): connot get default adapter yet');
|
||||
}
|
||||
};
|
||||
|
||||
// API v2 get adapter via bluetooth
|
||||
var _fetchAdapterV2 = function() {
|
||||
// need time to get bluetooth adapter at first run
|
||||
_bluetooth.onattributechanged = function onManagerAttributeChanged(evt) {
|
||||
for (var i in evt.attrs) {
|
||||
switch (evt.attrs[i]) {
|
||||
case 'defaultAdapter':
|
||||
console.log('defaultAdapter changed. address:',
|
||||
_bluetooth.defaultAdapter.address);
|
||||
_adapter = _bluetooth.defaultAdapter;
|
||||
_processCallbacks();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_adapter = _bluetooth.defaultAdapter;
|
||||
if (_adapter) {
|
||||
_processCallbacks();
|
||||
}
|
||||
};
|
||||
|
||||
// API v1 get adapter via bluetooth
|
||||
var _fetchAdapter = function() {
|
||||
var req = _bluetooth.getDefaultAdapter();
|
||||
if (req) {
|
||||
req.onsuccess = function() {
|
||||
if (_adapter) {
|
||||
_oldAdapter = _adapter;
|
||||
}
|
||||
|
||||
_isReady = true;
|
||||
_adapter = req.result;
|
||||
|
||||
// Put the callback function of onpairedstatuschanged to the new adapter
|
||||
// because the new adapter won't remember those callback function which
|
||||
// is registered before. In other word, we get a new adpater after
|
||||
// turned on/off Bluetooth. The new adapter have no registered callback.
|
||||
if (_oldAdapter && _oldAdapter.onpairedstatuschanged) {
|
||||
_adapter.onpairedstatuschanged = _oldAdapter.onpairedstatuschanged;
|
||||
}
|
||||
|
||||
_callbacks.forEach(function(callback) {
|
||||
callback();
|
||||
});
|
||||
// clean up the _callback queue
|
||||
_callbacks = [];
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
// We can do nothing without default adapter.
|
||||
console.log('BluetoothHelper(): connot get default adapter!!!');
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var _getAdapter = function() {
|
||||
if (_v2) {
|
||||
_fetchAdapterV2();
|
||||
} else {
|
||||
_fetchAdapter();
|
||||
}
|
||||
};
|
||||
|
||||
var _resetAdapter = function() {
|
||||
if (_adapter) {
|
||||
_oldAdapter = _adapter;
|
||||
}
|
||||
// clean up state and adapter
|
||||
_isReady = false;
|
||||
_adapter = null;
|
||||
};
|
||||
|
||||
// init
|
||||
if (_bluetooth) {
|
||||
// detect API version
|
||||
if (typeof(_bluetooth.onattributechanged) === 'undefined') {
|
||||
_v2 = false;
|
||||
}
|
||||
|
||||
if (_v2) {
|
||||
_bluetooth.onadapteradded = function onAdapterAdded(evt) {
|
||||
_getAdapter();
|
||||
};
|
||||
} else {
|
||||
_bluetooth.addEventListener('enabled', _getAdapter);
|
||||
_bluetooth.addEventListener('adapteradded', _getAdapter);
|
||||
_bluetooth.addEventListener('disabled', _resetAdapter);
|
||||
}
|
||||
_getAdapter();
|
||||
}
|
||||
|
||||
return {
|
||||
profiles: profiles,
|
||||
|
||||
answerWaitingCall: function() {
|
||||
_ready(function() {
|
||||
_adapter.answerWaitingCall();
|
||||
});
|
||||
},
|
||||
|
||||
ignoreWaitingCall: function() {
|
||||
_ready(function() {
|
||||
_adapter.ignoreWaitingCall();
|
||||
});
|
||||
},
|
||||
|
||||
toggleCalls: function() {
|
||||
_ready(function() {
|
||||
_adapter.toggleCalls();
|
||||
});
|
||||
},
|
||||
|
||||
getConnectedDevicesByProfile: function(profileID, cb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.getConnectedDevices(profileID), cb);
|
||||
});
|
||||
},
|
||||
|
||||
connectSco: function(cb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.connectSco(), cb);
|
||||
});
|
||||
},
|
||||
|
||||
disconnectSco: function(cb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.disconnectSco(), cb);
|
||||
});
|
||||
},
|
||||
|
||||
getPairedDevices: function(cb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.getPairedDevices(), cb);
|
||||
});
|
||||
},
|
||||
|
||||
getAddress: function(cb) {
|
||||
if (_v2) {
|
||||
console.log('getAddress function is deprecated');
|
||||
return;
|
||||
}
|
||||
|
||||
_ready(function() {
|
||||
var address = _adapter.address;
|
||||
cb(address);
|
||||
});
|
||||
},
|
||||
|
||||
setPairingConfirmation: function(address, confirmed) {
|
||||
if (_v2) {
|
||||
console.log('setPairingConfirmation API is deprecated');
|
||||
return;
|
||||
}
|
||||
|
||||
_ready(function() {
|
||||
_adapter.setPairingConfirmation(address, confirmed);
|
||||
});
|
||||
},
|
||||
|
||||
setPinCode: function(address, pincode) {
|
||||
if (_v2) {
|
||||
console.log('setPairingConfirmation API is deprecated');
|
||||
return;
|
||||
}
|
||||
|
||||
_ready(function() {
|
||||
_adapter.setPinCode(address, pincode);
|
||||
});
|
||||
},
|
||||
|
||||
setPasskey: function(address, key) {
|
||||
if (_v2) {
|
||||
console.log('setPairingConfirmation API is deprecated');
|
||||
return;
|
||||
}
|
||||
|
||||
_ready(function() {
|
||||
_adapter.setPasskey(address, key);
|
||||
});
|
||||
},
|
||||
|
||||
isScoConnected: function(cb, errorcb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.isScoConnected(), cb, errorcb);
|
||||
});
|
||||
},
|
||||
|
||||
sendMediaMetaData: function(metadata, cb, errorcb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.sendMediaMetaData(metadata), cb, errorcb);
|
||||
});
|
||||
},
|
||||
|
||||
sendMediaPlayStatus: function(metadata, cb, errorcb) {
|
||||
_ready(function() {
|
||||
_handleRequest(_adapter.sendMediaPlayStatus(metadata), cb, errorcb);
|
||||
});
|
||||
},
|
||||
|
||||
set onhfpstatuschanged(callback) {
|
||||
_ready(function() {
|
||||
_adapter.onhfpstatuschanged = callback;
|
||||
});
|
||||
},
|
||||
|
||||
set onscostatuschanged(callback) {
|
||||
_ready(function() {
|
||||
_adapter.onscostatuschanged = callback;
|
||||
});
|
||||
},
|
||||
|
||||
set ona2dpstatuschanged(callback) {
|
||||
_ready(function() {
|
||||
_adapter.ona2dpstatuschanged = callback;
|
||||
});
|
||||
},
|
||||
|
||||
set onrequestmediaplaystatus(callback) {
|
||||
_ready(function() {
|
||||
_adapter.onrequestmediaplaystatus = callback;
|
||||
});
|
||||
},
|
||||
|
||||
set onpairedstatuschanged(callback) {
|
||||
if (_v2) {
|
||||
console.log('onpairedstatuschanged API is deprecated');
|
||||
return;
|
||||
}
|
||||
|
||||
_ready(function() {
|
||||
_adapter.onpairedstatuschanged = callback;
|
||||
});
|
||||
},
|
||||
|
||||
v2: _v2, // expose API version for app reference
|
||||
|
||||
// bypass the enable/disable state if works in APIv1
|
||||
enable: function() {
|
||||
if (_v2) {
|
||||
_ready(function() {
|
||||
_adapter.enable();
|
||||
});
|
||||
} else {
|
||||
console.log('enable is not support in v1 API!');
|
||||
}
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if (_v2) {
|
||||
_ready(function() {
|
||||
_adapter.disable();
|
||||
});
|
||||
} else {
|
||||
console.log('disable is not support in v1 API!');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/* global mozIntl */
|
||||
|
||||
/**
|
||||
* This lib relies on `l10n.js' to implement localizable date/time strings.
|
||||
*
|
||||
* The proposed `DownloadFormatter' object provides features for formatting
|
||||
* the data retrieved from the new API for Downloads, taking into account
|
||||
* the structure defined by the API itself.
|
||||
*/
|
||||
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var NUMBER_OF_DECIMALS = 2;
|
||||
var BYTE_SCALE = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
function _getFormattedSize(bytes) {
|
||||
if (bytes === undefined || isNaN(bytes)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
while (bytes >= 1024 && index < BYTE_SCALE.length) {
|
||||
bytes /= 1024;
|
||||
++index;
|
||||
}
|
||||
|
||||
return document.l10n.
|
||||
formatValue('byteUnit-' + BYTE_SCALE[index]).then(unit => {
|
||||
return document.l10n.formatValue('fileSize', {
|
||||
size: bytes.toFixed(NUMBER_OF_DECIMALS),
|
||||
unit: unit
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _calcPercentage(currently, total) {
|
||||
if (total === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseInt((100 * currently) / total);
|
||||
}
|
||||
|
||||
var DownloadFormatter = {
|
||||
getFormattedSize: function(bytes) {
|
||||
return _getFormattedSize(bytes);
|
||||
},
|
||||
getPercentage: function(download) {
|
||||
return _calcPercentage(download.currentBytes, download.totalBytes);
|
||||
},
|
||||
getFileName: function(download) {
|
||||
return download.path.split('/').pop(); // filename.ext
|
||||
},
|
||||
getTotalSize: function(download) {
|
||||
var bytes = download.totalBytes;
|
||||
return _getFormattedSize(bytes);
|
||||
},
|
||||
getDownloadedSize: function(download) {
|
||||
var bytes = download.currentBytes;
|
||||
return _getFormattedSize(bytes);
|
||||
},
|
||||
getDate: function(download) {
|
||||
var date;
|
||||
|
||||
try {
|
||||
date = download.startTime;
|
||||
} catch (ex) {
|
||||
date = new Date();
|
||||
console.error(ex);
|
||||
}
|
||||
|
||||
var formatter = mozIntl._gaia.RelativeDate(navigator.languages);
|
||||
return formatter.format(date);
|
||||
},
|
||||
getUUID: function(download) {
|
||||
return download.id || this.getFileName(download);
|
||||
}
|
||||
};
|
||||
|
||||
exports.DownloadFormatter = DownloadFormatter;
|
||||
|
||||
}(this));
|
||||
|
|
@ -0,0 +1,685 @@
|
|||
'use strict';
|
||||
/* exported DownloadHelper */
|
||||
/* global DownloadFormatter */
|
||||
/* global DownloadStore */
|
||||
/* global DownloadUI */
|
||||
/* global LazyLoader */
|
||||
/* global MimeMapper */
|
||||
/* global MozActivity */
|
||||
/* global SettingsListener */
|
||||
|
||||
/*
|
||||
* DownloadHelper.js: Perform some utility functions with DOMDownload
|
||||
* objects.
|
||||
*
|
||||
* - You have to include in you HTML:
|
||||
*
|
||||
* <!-- <script src="shared/js/mime_mapper.js"></script> -->
|
||||
* <!-- <script src="shared/js/download/download_store.js"></script> -->
|
||||
* <script defer src="shared/js/lazy_loader.js"></script>
|
||||
*
|
||||
* - How to use this component.
|
||||
*
|
||||
* For launching a download
|
||||
*
|
||||
* var req = DownloadHelper.open(download);
|
||||
*
|
||||
* req.onsuccess = function req_onsuccess() {
|
||||
* alert('The download was opened so we can remove the notification');
|
||||
* }
|
||||
*
|
||||
* req.onerror = function req_onerror() {
|
||||
* alert('Something wrong!: ' + req.error.message);
|
||||
* }
|
||||
*
|
||||
*/
|
||||
var DownloadHelper = (function() {
|
||||
|
||||
// Exception code constants
|
||||
var CODE = {
|
||||
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
|
||||
DEVICE_STORAGE: 'DEVICE_STORAGE',
|
||||
MIME_TYPE_NOT_SUPPORTED: 'MIME_TYPE_NOT_SUPPORTED',
|
||||
INVALID_STATE: 'INVALID_STATE',
|
||||
NO_SDCARD: 'NO_SDCARD',
|
||||
NO_PROVIDER: 'NO_PROVIDER',
|
||||
UNMOUNTED_SDCARD: 'UNMOUNTED_SDCARD'
|
||||
};
|
||||
|
||||
/*
|
||||
* Request auxiliary object to support asynchronous calls
|
||||
*/
|
||||
var Request = function() {
|
||||
this.done = function(result) {
|
||||
if (typeof this.onsuccess === 'function') {
|
||||
this.result = result;
|
||||
window.setTimeout(function() {
|
||||
this.onsuccess({
|
||||
target: this
|
||||
});
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
this.failed = function(error) {
|
||||
if (typeof this.onerror === 'function') {
|
||||
this.error = error;
|
||||
window.setTimeout(function() {
|
||||
this.onerror({
|
||||
target: this
|
||||
});
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Storage name by default
|
||||
var STORAGE_NAME_BY_DEFAULT = 'sdcard';
|
||||
|
||||
// Storage name settings key
|
||||
var STORAGE_NAME_KEY = 'device.storage.writable.name';
|
||||
|
||||
// Current storage name
|
||||
var storageName = STORAGE_NAME_BY_DEFAULT;
|
||||
|
||||
LazyLoader.load('shared/js/settings_listener.js', function settingsLoaded() {
|
||||
SettingsListener.observe(STORAGE_NAME_KEY, STORAGE_NAME_BY_DEFAULT,
|
||||
function setStorageName(evt) {
|
||||
var settingValue = evt.settingValue;
|
||||
if (settingValue) {
|
||||
storageName = settingValue;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* Error auxiliary method
|
||||
*/
|
||||
function sendError(req, message, code) {
|
||||
req.failed({
|
||||
message: message,
|
||||
code: code
|
||||
});
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a blob from the sdcard
|
||||
*
|
||||
* @param{Object} The download object
|
||||
*
|
||||
*/
|
||||
function getBlob(download) {
|
||||
var req = new Request();
|
||||
var storage = getVolume(download.storageName);
|
||||
var storeAvailableReq = storage.available();
|
||||
|
||||
storeAvailableReq.onsuccess = function available_onsuccess(e) {
|
||||
var path = download.storagePath;
|
||||
switch (storeAvailableReq.result) {
|
||||
case 'unavailable':
|
||||
sendError(req, ' Could not open the file: ' + path + ' from ' +
|
||||
download.storageName, CODE.NO_SDCARD);
|
||||
break;
|
||||
|
||||
case 'shared':
|
||||
sendError(req, ' Could not open the file: ' + path + ' from ' +
|
||||
download.storageName, CODE.UNMOUNTED_SDCARD);
|
||||
break;
|
||||
|
||||
default:
|
||||
try {
|
||||
var storeGetReq = storage.get(path);
|
||||
|
||||
storeGetReq.onsuccess = function store_onsuccess() {
|
||||
req.done(storeGetReq.result);
|
||||
};
|
||||
|
||||
storeGetReq.onerror = function store_onerror() {
|
||||
sendError(req, storeGetReq.error.name +
|
||||
' Could not open the file: ' + path + ' from ' +
|
||||
download.storageName, CODE.FILE_NOT_FOUND);
|
||||
};
|
||||
} catch (ex) {
|
||||
sendError(req, 'Error getting the file ' + path + ' from ' +
|
||||
download.storageName, CODE.DEVICE_STORAGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
storeAvailableReq.onerror = function available_onerror() {
|
||||
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
|
||||
};
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/*
|
||||
* Base action that implements the commom logic for actions based on the
|
||||
* MozActivity API
|
||||
*
|
||||
* @param{Object} Configuration parameters
|
||||
*/
|
||||
var Action = function Action(params) {
|
||||
this.req = params.request;
|
||||
this.name = params.actionType.activityName;
|
||||
};
|
||||
|
||||
Action.prototype = {
|
||||
/*
|
||||
* It performs the action based on MozActivity
|
||||
*/
|
||||
run: function a_run() {
|
||||
var activity = new MozActivity({
|
||||
name: this.name,
|
||||
data: this.data
|
||||
});
|
||||
|
||||
activity.onsuccess = this._onsuccess.bind(this);
|
||||
activity.onerror = this._onerror.bind(this);
|
||||
},
|
||||
|
||||
/*
|
||||
* This method implements the generic onsuccess callback
|
||||
*/
|
||||
_onsuccess: function a_onsuccess() {
|
||||
this.req.done();
|
||||
},
|
||||
|
||||
/*
|
||||
* This method implements the generic onerror callback
|
||||
*/
|
||||
_onerror: function a_onerror(evt) {
|
||||
this.req.failed({
|
||||
message: evt.target.error.name
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* This action opens downloads extending the Action Object
|
||||
*
|
||||
* @param{Object} Configuration parameters
|
||||
*/
|
||||
var OpenAction = function OpenAction(params) {
|
||||
Action.call(this, params);
|
||||
|
||||
this.data = {
|
||||
url: params.download.path,
|
||||
filename: DownloadFormatter.getFileName(params.download),
|
||||
type: params.type,
|
||||
blob: params.blob
|
||||
};
|
||||
};
|
||||
|
||||
OpenAction.prototype = {
|
||||
__proto__: Action.prototype
|
||||
};
|
||||
|
||||
/*
|
||||
* This action shares downloads extending the Action Object
|
||||
*
|
||||
* @param{Object} Configuration parameters
|
||||
*/
|
||||
var ShareAction = function ShareAction(params) {
|
||||
Action.call(this, params);
|
||||
|
||||
this.data = {
|
||||
// 'share' activities do not work with specific mime types
|
||||
type: params.type.split('/')[0] + '/*',
|
||||
blobs: [params.blob],
|
||||
filenames: [DownloadFormatter.getFileName(params.download)]
|
||||
};
|
||||
};
|
||||
|
||||
ShareAction.prototype = {
|
||||
__proto__: Action.prototype,
|
||||
|
||||
/*
|
||||
* It overrides the generic onerror callback for another more suitable
|
||||
*/
|
||||
_onerror: function sa_onerror(evt) {
|
||||
if (evt.target.error.name !== 'NO_PROVIDER') {
|
||||
return;
|
||||
}
|
||||
|
||||
sendError(this.req, 'No provider to share file', CODE.NO_PROVIDER);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* This action gets the info for a download
|
||||
*
|
||||
* @param{Object} Configuration parameters
|
||||
*/
|
||||
var InfoAction = function InfoAction(params) {
|
||||
Action.call(this, params);
|
||||
|
||||
this.data = {
|
||||
name: DownloadFormatter.getFileName(params.download),
|
||||
type: params.type,
|
||||
blob: params.blob,
|
||||
size: params.download.totalBytes,
|
||||
path: params.download.path
|
||||
};
|
||||
};
|
||||
|
||||
InfoAction.prototype = {
|
||||
__proto__: Action.prototype,
|
||||
|
||||
/*
|
||||
* It overrides the generic run method
|
||||
*/
|
||||
run: function ia_run() {
|
||||
this.req.done(this.data);
|
||||
}
|
||||
};
|
||||
|
||||
// This is a factory that deals with different <Action> objects
|
||||
var ActionsFactory = {
|
||||
TYPE: {
|
||||
OPEN: {
|
||||
activityName: 'open',
|
||||
actionClass: OpenAction
|
||||
},
|
||||
SHARE: {
|
||||
activityName: 'share',
|
||||
actionClass: ShareAction
|
||||
},
|
||||
INFO: {
|
||||
actionClass: InfoAction
|
||||
},
|
||||
WALLPAPER: {
|
||||
activityName: 'setwallpaper',
|
||||
actionClass: ShareAction
|
||||
},
|
||||
RINGTONE: {
|
||||
activityName: 'setringtone',
|
||||
actionClass: ShareAction
|
||||
}
|
||||
},
|
||||
|
||||
create: function af_create(params) {
|
||||
return new params.actionType.actionClass(params);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the mime type
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*
|
||||
* @returns(String) Mime type
|
||||
*/
|
||||
function getType(download) {
|
||||
var fileName = DownloadFormatter.getFileName(download);
|
||||
var type = MimeMapper.guessTypeFromFileProperties(fileName,
|
||||
download.contentType);
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method allows third-parties to open or share downloads
|
||||
*
|
||||
* @param{Object} Action types: <ActionsFactory.TYPE.OPEN> or
|
||||
* <ActionsFactory.TYPE.SHARE>
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
function runAction(actionType, download) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function launching() {
|
||||
var state = download.state;
|
||||
if (state === 'succeeded' || state === 'finalized') {
|
||||
LazyLoader.load(['shared/js/mime_mapper.js',
|
||||
'shared/js/download/download_formatter.js'],
|
||||
function loaded() {
|
||||
var type = getType(download);
|
||||
|
||||
//
|
||||
// The 'open' action will always launch an activity using the original
|
||||
// content type to allow for third party applications to handle
|
||||
// arbitrary types of content.
|
||||
//
|
||||
// The 'share' action on the other hand only works with known mime
|
||||
// types at this time.
|
||||
//
|
||||
|
||||
if (type.length === 0) {
|
||||
type = download.contentType;
|
||||
|
||||
if (actionType !== ActionsFactory.TYPE.OPEN &&
|
||||
actionType !== ActionsFactory.TYPE.INFO) {
|
||||
sendError(req, 'Mime type not supported: ' + type,
|
||||
CODE.MIME_TYPE_NOT_SUPPORTED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var blobReq = getBlob(download);
|
||||
|
||||
blobReq.onsuccess = function() {
|
||||
ActionsFactory.create({
|
||||
actionType: actionType,
|
||||
download: download,
|
||||
type: type,
|
||||
blob: blobReq.result,
|
||||
request: req
|
||||
}).run();
|
||||
};
|
||||
|
||||
blobReq.onerror = function() {
|
||||
// Problem getting the blob from the sdcard
|
||||
req.failed(blobReq.error);
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendError(req, 'Becareful, the download is not finished!',
|
||||
CODE.INVALID_STATE);
|
||||
}, 0);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method allows clients to remove a download, from the
|
||||
* list and the phone.
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
function remove(download) {
|
||||
var req = new Request();
|
||||
var incompleteDownload = download.state !== 'succeeded';
|
||||
// If is not done, use download manager to remove it,
|
||||
// otherwise, deal with the datastore.
|
||||
setTimeout(function() {
|
||||
if (incompleteDownload) {
|
||||
if (!navigator.mozDownloadManager) {
|
||||
sendError(req, 'DownloadManager not present', CODE.INVALID_STATE);
|
||||
} else {
|
||||
// First we pause the download so that everyone knows it's being
|
||||
// stopped. The Downloads API itself won't stop the download first,
|
||||
// it will simply kill it.
|
||||
// XXXAus: Remove when we fix bug #1090551
|
||||
download.pause().then(
|
||||
function() {
|
||||
navigator.mozDownloadManager.remove(download).then(
|
||||
function success() {
|
||||
req.done(download);
|
||||
},
|
||||
function error() {
|
||||
sendError(req,
|
||||
'DownloadManager doesnt know about this download',
|
||||
CODE.INVALID_STATE);
|
||||
}
|
||||
);
|
||||
},
|
||||
function() {
|
||||
sendError(req,
|
||||
'Failed to pause download before removal',
|
||||
CODE.INVALID_STATE);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
req.done(download);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return incompleteDownload ? req : doRemoveFromPhone(req, download);
|
||||
}
|
||||
|
||||
/*
|
||||
* Performs the proper delete of the physical file, also
|
||||
* from the datastore if the download has finished.
|
||||
*
|
||||
* @param{Object} This is the Request object
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
function doRemoveFromPhone(deleteRequest, download) {
|
||||
var req = new Request();
|
||||
|
||||
deleteRequest.onsuccess = function() {
|
||||
var storage = getVolume(download.storageName);
|
||||
var storeAvailableReq = storage.available();
|
||||
|
||||
storeAvailableReq.onsuccess = function available_onsuccess(e) {
|
||||
var path = download.path;
|
||||
switch (storeAvailableReq.result) {
|
||||
case 'unavailable':
|
||||
sendError(req, ' Could not delete the file: ' + path + ' from ' +
|
||||
storageName, CODE.NO_SDCARD);
|
||||
break;
|
||||
|
||||
case 'shared':
|
||||
sendError(req, ' Could not delete the file: ' + path + ' from ' +
|
||||
storageName, CODE.UNMOUNTED_SDCARD);
|
||||
break;
|
||||
|
||||
default:
|
||||
var storeDeleteReq = storage.delete(download.storagePath);
|
||||
|
||||
storeDeleteReq.onsuccess = function store_onsuccess() {
|
||||
// Remove from the datastore if status is 'succeeded'
|
||||
// if we find any problem with the datastore, don't send
|
||||
// an error, since the physical remove already happened
|
||||
if (download.state === 'succeeded') {
|
||||
LazyLoader.load(['/shared/js/download/download_store.js'],
|
||||
function() {
|
||||
DownloadStore.remove(download);
|
||||
}
|
||||
);
|
||||
}
|
||||
req.done(storeDeleteReq.result);
|
||||
};
|
||||
|
||||
storeDeleteReq.onerror = function store_onerror() {
|
||||
sendError(req, storeDeleteReq.error.name +
|
||||
' Could not remove the file: ' + download.path + ' from ' +
|
||||
storageName, CODE.FILE_NOT_FOUND);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
storeAvailableReq.onerror = function available_onerror() {
|
||||
sendError(req, 'Error getting storage state ', CODE.DEVICE_STORAGE);
|
||||
};
|
||||
};
|
||||
|
||||
deleteRequest.onerror = function(error) {
|
||||
sendError(req, error.message, error.code);
|
||||
};
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function handlerError(error, download, cb) {
|
||||
LazyLoader.load('shared/js/download/download_ui.js', (function loaded() {
|
||||
var req;
|
||||
var show = DownloadUI.show;
|
||||
|
||||
// Canceled activites are normal and shouldn't be interpreted as errors.
|
||||
// Unfortunately, this isn't reported in a standard way by our
|
||||
// applications (or third party apps for that matter). This is why we
|
||||
// have this lazy filter here that may need to be updated in the future
|
||||
// but hopefully will just get removed.
|
||||
if (error.message &&
|
||||
(error.message.endsWith('canceled') ||
|
||||
error.message.endsWith('cancelled'))) {
|
||||
// Since this isn't actually an error, we invoke the callback with null.
|
||||
cb && cb(null);
|
||||
// and return early.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case CODE.NO_SDCARD:
|
||||
case CODE.UNMOUNTED_SDCARD:
|
||||
case CODE.FILE_NOT_FOUND:
|
||||
case CODE.NO_PROVIDER:
|
||||
req = show(DownloadUI.TYPE[error.code], download, true);
|
||||
req.onconfirm = cb;
|
||||
|
||||
break;
|
||||
|
||||
case CODE.MIME_TYPE_NOT_SUPPORTED:
|
||||
req = show(DownloadUI.TYPE.UNSUPPORTED_FILE_TYPE, download, true);
|
||||
req.onconfirm = function tuftOnConfirm() {
|
||||
showRemoveFileUI(download, cb);
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
req = show(DownloadUI.TYPE.FILE_OPEN_ERROR, download, true);
|
||||
req.onconfirm = function tfoeOnConfirm() {
|
||||
showRemoveFileUI(download, cb);
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// We have to remove the notification if the user cancels
|
||||
req.oncancel = cb;
|
||||
}));
|
||||
}
|
||||
|
||||
function showRemoveFileUI(download, cb) {
|
||||
var req = DownloadUI.show(DownloadUI.TYPE.DELETE, download, true);
|
||||
|
||||
req.oncancel = cb;
|
||||
|
||||
req.onconfirm = function doRemove() {
|
||||
if (typeof cb === 'function') {
|
||||
cb(download);
|
||||
} else {
|
||||
remove(download);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getFreeSpace(cb) {
|
||||
var storage = navigator.getDeviceStorage(storageName);
|
||||
|
||||
if (!storage) {
|
||||
console.error('Cannot get free space size in sdcard');
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var req = storage.freeSpace();
|
||||
|
||||
req.onsuccess = function(e) {
|
||||
cb(e.target.result);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
cb(null);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the storage for the download based on the volumen
|
||||
* it was saved (storageName)
|
||||
*/
|
||||
function getVolume(volumeName) {
|
||||
// Per API design, all media type return the same volumes.
|
||||
// So we use 'sdcard' here for no reason.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=856782#c10
|
||||
var volumes = navigator.getDeviceStorages('sdcard');
|
||||
if (!volumeName || volumeName === '') {
|
||||
return volumes[0];
|
||||
}
|
||||
for (var i = 0; i < volumes.length; ++i) {
|
||||
if (volumes[i].storageName === volumeName) {
|
||||
return volumes[i];
|
||||
}
|
||||
}
|
||||
return volumes[0];
|
||||
}
|
||||
|
||||
return {
|
||||
/*
|
||||
* This method allows clients to open a downlaod
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
open: function(download) {
|
||||
return runAction(ActionsFactory.TYPE.OPEN, download);
|
||||
},
|
||||
|
||||
/*
|
||||
* This method allows clients to share a downlaoded file
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
share: function(download) {
|
||||
return runAction(ActionsFactory.TYPE.SHARE, download);
|
||||
},
|
||||
|
||||
/*
|
||||
* This method allows clients to set as wallaper a downlaoded file
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
wallpaper: function(download) {
|
||||
return runAction(ActionsFactory.TYPE.WALLPAPER, download);
|
||||
},
|
||||
|
||||
/*
|
||||
* This method allows clients to set as ringtone a downlaoded file
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
ringtone: function(download) {
|
||||
return runAction(ActionsFactory.TYPE.RINGTONE, download);
|
||||
},
|
||||
|
||||
/*
|
||||
* This method returns information about a download
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
info: function(download) {
|
||||
return runAction(ActionsFactory.TYPE.INFO, download);
|
||||
},
|
||||
|
||||
/*
|
||||
* Given a download, remove it from the DownloadManager
|
||||
* list, and the file system.
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*/
|
||||
remove: remove,
|
||||
|
||||
/*
|
||||
* Returns exception code constants
|
||||
*/
|
||||
get CODE() {
|
||||
return CODE;
|
||||
},
|
||||
|
||||
/*
|
||||
* This method handles different errors when users attemp to open files
|
||||
*
|
||||
* @param{Object} Error object
|
||||
*
|
||||
* @param{Object} It represents a DOMDownload object
|
||||
*
|
||||
* @param{Function} This function is performed when the flow is finished
|
||||
*/
|
||||
handlerError: handlerError,
|
||||
|
||||
/*
|
||||
* Returns the free memory size in bytes
|
||||
*
|
||||
* @param{Function} This function is performed when the free memory size has
|
||||
* been calculated
|
||||
*/
|
||||
getFreeSpace: getFreeSpace
|
||||
};
|
||||
}());
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
'use strict';
|
||||
/* jshint moz:true */
|
||||
/* global Promise */
|
||||
/* global indexedDB */
|
||||
|
||||
/*
|
||||
* DownloadStore.js: Allows to store download finished ordered by timestamp
|
||||
*
|
||||
* - Adding a new download:
|
||||
*
|
||||
* var req = DownloadStore.add(download);
|
||||
*
|
||||
* req.onsuccess = function() {
|
||||
* console.log('Complete download stored successfully');
|
||||
* };
|
||||
*
|
||||
* req.onerror = function(e) {
|
||||
* console.error(e);
|
||||
* };
|
||||
*
|
||||
* - Getting all downloads:
|
||||
*
|
||||
* var reqStore = DownloadStore.getAll();
|
||||
*
|
||||
* reqStore.onsuccess = function(ev) {
|
||||
* ev.target.result.forEach(function(download) {
|
||||
* // Do something cool with the download
|
||||
* });
|
||||
* };
|
||||
*
|
||||
* reqStore.onerror = function(e) {
|
||||
* console.error(e);
|
||||
* };
|
||||
*
|
||||
* - Removing a existing download:
|
||||
*
|
||||
* var req = DownloadStore.remove(download);
|
||||
*
|
||||
* req.onsuccess = function() {
|
||||
* console.log('Deleted download successfully');
|
||||
* };
|
||||
*
|
||||
* req.onerror = function(e) {
|
||||
* console.error(e);
|
||||
* };
|
||||
*
|
||||
* WARNING:
|
||||
*
|
||||
* The system app should add this to its manifest file
|
||||
*
|
||||
* "datastores-owned": {
|
||||
* "download_store": {
|
||||
* "description": "Stores download finished"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* The settings app should add this to its manifest file
|
||||
*
|
||||
* "datastores-access": {
|
||||
* "download_store": {
|
||||
* "description": "Stores download finished"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
/* exported DownloadStore */
|
||||
var DownloadStore = (function() {
|
||||
var datastore;
|
||||
|
||||
// Datastore name declared on the manifest.webapp
|
||||
const DATASTORE_NAME = 'download_store';
|
||||
|
||||
var downloadListDB;
|
||||
var downloadList = [];
|
||||
var lastRevisionId;
|
||||
|
||||
const LIST_DB = 'download_list';
|
||||
const LIST_STORE = 'download_list';
|
||||
|
||||
const LIST_KEY = 0;
|
||||
const REVISION_KEY = 1;
|
||||
|
||||
/*
|
||||
* Request auxiliary object to support asynchronous calls
|
||||
*/
|
||||
var Request = function() {
|
||||
this.done = function(result) {
|
||||
if (typeof this.onsuccess === 'function') {
|
||||
this.result = result;
|
||||
window.setTimeout(function() {
|
||||
this.onsuccess({
|
||||
target: this
|
||||
});
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
this.failed = function(error) {
|
||||
if (typeof this.onerror === 'function') {
|
||||
this.error = error;
|
||||
window.setTimeout(function() {
|
||||
this.onerror({
|
||||
target: this
|
||||
});
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function promisify(request) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
request.onsuccess = (event) => {resolve(event.target.result);};
|
||||
request.onerror = (event) => {reject(event.target.errorCode);};
|
||||
});
|
||||
}
|
||||
|
||||
// Indicates the initialization state
|
||||
var readyState;
|
||||
|
||||
function init(success, fail) {
|
||||
if (readyState === 'initialized') {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
if (readyState === 'initializing') {
|
||||
document.addEventListener('ds-initialized', function oninitalized() {
|
||||
document.removeEventListener('ds-initialized', oninitalized);
|
||||
success();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
readyState = 'initializing';
|
||||
|
||||
if (!navigator.getDataStores) {
|
||||
var messageError = 'Data store: DataStore API is not working';
|
||||
console.error(messageError);
|
||||
fail({
|
||||
message: messageError
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.getDataStores(DATASTORE_NAME).then(function(ds) {
|
||||
var messageError = 'Download Store: Cannot get access to the DataStore';
|
||||
if (ds.length < 1) {
|
||||
console.error(messageError);
|
||||
throw ({
|
||||
message: messageError
|
||||
});
|
||||
}
|
||||
|
||||
datastore = ds[0];
|
||||
// Checking the length as the index should be there
|
||||
}).then(function() {
|
||||
return openDownloadListIndexedDB();
|
||||
}).then(function(db) {
|
||||
downloadListDB = db;
|
||||
|
||||
var listStore = downloadListDB.transaction(LIST_STORE, 'readonly')
|
||||
.objectStore(LIST_STORE);
|
||||
|
||||
return Promise.all([
|
||||
promisify(listStore.get(LIST_KEY))
|
||||
.then((value) => {downloadList = value;}),
|
||||
promisify(listStore.get(REVISION_KEY))
|
||||
.then((value) => {lastRevisionId = value;})
|
||||
]);
|
||||
}).then(function() {
|
||||
readyState = 'initialized';
|
||||
|
||||
// Remove the legacy index element, if it exists. See Bug 1180250.
|
||||
const LEGACY_INDEX_ID = 1;
|
||||
if (downloadList && downloadList.includes(LEGACY_INDEX_ID)) {
|
||||
return promisify(get(LEGACY_INDEX_ID)).then((download) => {
|
||||
if ('byTimestamp' in download) {
|
||||
downloadList.splice(downloadList.indexOf(LEGACY_INDEX_ID), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(function() {
|
||||
notifyOpenSuccess(success);
|
||||
}).catch(function(e) {
|
||||
console.error('Error while opening the Download Store: ', e.message);
|
||||
fail(e);
|
||||
});
|
||||
}
|
||||
|
||||
function openDownloadListIndexedDB() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = indexedDB.open(LIST_DB);
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
var db = event.target.result;
|
||||
resolve(db);
|
||||
};
|
||||
|
||||
request.onerror = function(event) {
|
||||
reject(event.target.errorCode);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = function(event) {
|
||||
var db = event.target.result;
|
||||
|
||||
promisify(db.createObjectStore(LIST_STORE)).then(() => {
|
||||
var listStore = db.transaction(LIST_STORE, 'readwrite')
|
||||
.objectStore(LIST_STORE);
|
||||
|
||||
return Promise.all([
|
||||
promisify(listStore.add([], LIST_KEY)),
|
||||
promisify(listStore.add(0, REVISION_KEY))
|
||||
]);
|
||||
}).catch(reject);
|
||||
};
|
||||
|
||||
request.onblocked = function(event) {
|
||||
reject(event.target.errorCode);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function notifyOpenSuccess(cb) {
|
||||
window.setTimeout(cb, 0);
|
||||
document.dispatchEvent(new CustomEvent('ds-initialized'));
|
||||
}
|
||||
|
||||
// These fields will be stored in our datastore
|
||||
var fieldsToPropagate = ['url', 'path', 'totalBytes', 'contentType',
|
||||
'startTime', 'state', 'storageName', 'storagePath'];
|
||||
|
||||
function cookDownload(download) {
|
||||
var ret = Object.create(null);
|
||||
|
||||
fieldsToPropagate.forEach(function(field) {
|
||||
ret[field] = download[field];
|
||||
});
|
||||
|
||||
// This is the timestamp when the download finished
|
||||
ret.finalizeTime = new Date();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function defaultError(req) {
|
||||
return defaultErrorCb.bind(null, req);
|
||||
}
|
||||
|
||||
function defaultErrorCb(request, error) {
|
||||
request.failed(error);
|
||||
}
|
||||
|
||||
function defaultSuccess(req) {
|
||||
return defaultSuccessCb.bind(null, req);
|
||||
}
|
||||
|
||||
function defaultSuccessCb(request, result) {
|
||||
request.done(result);
|
||||
}
|
||||
|
||||
function updateDownloadList() {
|
||||
var cursor = datastore.sync(lastRevisionId);
|
||||
|
||||
function cursorResolve(task) {
|
||||
switch (task.operation) {
|
||||
case 'done':
|
||||
lastRevisionId = task.revisionId;
|
||||
|
||||
var listStore = downloadListDB.transaction(LIST_STORE, 'readwrite')
|
||||
.objectStore(LIST_STORE);
|
||||
|
||||
return Promise.all([
|
||||
promisify(listStore.put(downloadList, LIST_KEY)),
|
||||
promisify(listStore.put(lastRevisionId, REVISION_KEY))
|
||||
]);
|
||||
|
||||
case 'clear':
|
||||
downloadList = [];
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
downloadList.push(task.id);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
// This doesn't matter for us.
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
var i = downloadList.indexOf(task.id);
|
||||
if (i >= 0) {
|
||||
downloadList.splice(i, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return cursor.next().then(cursorResolve);
|
||||
}
|
||||
|
||||
return cursor.next().then(cursorResolve);
|
||||
}
|
||||
|
||||
function doAdd(download, req) {
|
||||
var downloadCooked = cookDownload(download);
|
||||
// Adding our cooked download
|
||||
datastore.add(downloadCooked).then(function(id) {
|
||||
// Enriched object with the id provided by the datastore
|
||||
downloadCooked.id = id;
|
||||
datastore.put(downloadCooked, id)
|
||||
.then(function() { req.done(downloadCooked); },
|
||||
defaultError(req));
|
||||
}, defaultError(req));
|
||||
}
|
||||
|
||||
function add(download) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doAdd.bind(null, download, req), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function doGet(id, req) {
|
||||
datastore.get(id).then(function(download) { req.done(download); },
|
||||
defaultError(req));
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doGet.bind(null, id, req), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function doGetAll(req) {
|
||||
updateDownloadList().then(function() {
|
||||
datastore.get.apply(datastore, downloadList).then(defaultSuccess(req),
|
||||
defaultError(req));
|
||||
}, defaultError(req));
|
||||
}
|
||||
|
||||
function getAll() {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doGetAll.bind(null, req), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function doRemove(id, req) {
|
||||
// Removing the download object from datastore
|
||||
datastore.remove(id).then(function(success) {
|
||||
if (success) {
|
||||
defaultSuccess(req);
|
||||
} else {
|
||||
req.failed({
|
||||
message: 'The download with id: ' + id + 'does not exist'
|
||||
});
|
||||
}
|
||||
}, defaultError(req));
|
||||
}
|
||||
|
||||
function remove(download) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doRemove.bind(null, download.id, req), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function doAddListener(listener) {
|
||||
datastore.addEventListener('change', listener);
|
||||
}
|
||||
|
||||
function addListener(listener) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doAddListener.bind(null, listener), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function doRemoveListener(listener) {
|
||||
datastore.removeEventListener('change', listener);
|
||||
}
|
||||
|
||||
function removeListener(listener) {
|
||||
var req = new Request();
|
||||
|
||||
window.setTimeout(function() {
|
||||
init(doRemoveListener.bind(null, listener), req.failed.bind(req));
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
return {
|
||||
/*
|
||||
* This method returns the download with the specified id.
|
||||
*/
|
||||
get: get,
|
||||
|
||||
/*
|
||||
* This method returns an array of complete downloads
|
||||
*/
|
||||
getAll: getAll,
|
||||
|
||||
/*
|
||||
* It adds a new download object and returns the new datastore
|
||||
* download object
|
||||
*
|
||||
* @param{Object} Download object provided by the API
|
||||
*
|
||||
*/
|
||||
add: add,
|
||||
|
||||
/*
|
||||
* It removes a download object
|
||||
*
|
||||
* @param{Object} The download object to be removed from the datastore
|
||||
*
|
||||
*/
|
||||
remove: remove,
|
||||
|
||||
/*
|
||||
* Add a listener that will receive datastore change events.
|
||||
*
|
||||
* @param{Function} The listener to be added.
|
||||
*/
|
||||
addListener: addListener,
|
||||
|
||||
/*
|
||||
* Remove a listener previously added via addListener.
|
||||
*
|
||||
* @param{Function} The listener to be removed.
|
||||
*/
|
||||
removeListener: removeListener
|
||||
};
|
||||
}());
|
||||
154
assets/webapps/uitest.gaiamobile.org/shared/js/lazy_loader.js
Normal file
154
assets/webapps/uitest.gaiamobile.org/shared/js/lazy_loader.js
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/* exported LazyLoader */
|
||||
/* globals HtmlImports, Promise */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This contains a simple LazyLoader implementation
|
||||
* To use:
|
||||
*
|
||||
* LazyLoader.load(
|
||||
* ['/path/to/file.js', '/path/to/file.css', 'domNode'], callback
|
||||
* );
|
||||
*/
|
||||
var LazyLoader = (function() {
|
||||
|
||||
function LazyLoader() {
|
||||
this._loaded = {};
|
||||
this._isLoading = {};
|
||||
}
|
||||
|
||||
LazyLoader.prototype = {
|
||||
|
||||
_js: function(file, callback) {
|
||||
var script = document.createElement('script');
|
||||
script.src = file;
|
||||
// until bug 916255 lands async is the default so
|
||||
// we must disable it so scripts load in the order they where
|
||||
// required.
|
||||
script.async = false;
|
||||
script.addEventListener('load', callback);
|
||||
document.head.appendChild(script);
|
||||
this._isLoading[file] = script;
|
||||
},
|
||||
|
||||
_css: function(file, callback) {
|
||||
var style = document.createElement('link');
|
||||
style.type = 'text/css';
|
||||
style.rel = 'stylesheet';
|
||||
style.href = file;
|
||||
document.head.appendChild(style);
|
||||
callback();
|
||||
},
|
||||
|
||||
_html: function(domNode, callback) {
|
||||
|
||||
// The next few lines are for loading html imports in DEBUG mode
|
||||
if (domNode.getAttribute('is')) {
|
||||
this.load(['/shared/js/html_imports.js'], function() {
|
||||
HtmlImports.populate(callback);
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < domNode.childNodes.length; i++) {
|
||||
if (domNode.childNodes[i].nodeType == document.COMMENT_NODE) {
|
||||
domNode.innerHTML = domNode.childNodes[i].nodeValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('lazyload', {
|
||||
detail: domNode
|
||||
}));
|
||||
|
||||
callback();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves content of JSON file.
|
||||
*
|
||||
* @param {String} file Path to JSON file
|
||||
* @param {Boolean} mozSystem If xhr should use mozSystem permissions
|
||||
* @return {Promise} A promise that resolves to the JSON content
|
||||
* or null in case of invalid path. Rejects if an error occurs.
|
||||
*/
|
||||
getJSON: function(file, mozSystem) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr;
|
||||
if (mozSystem) {
|
||||
xhr = new XMLHttpRequest({mozSystem: true});
|
||||
} else {
|
||||
xhr = new XMLHttpRequest();
|
||||
}
|
||||
xhr.open('GET', file, true);
|
||||
xhr.responseType = 'json';
|
||||
|
||||
xhr.onerror = function(error) {
|
||||
reject(error);
|
||||
};
|
||||
xhr.onload = function() {
|
||||
if (xhr.response !== null) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject(new Error('No valid JSON object was found (' +
|
||||
xhr.status + ' ' + xhr.statusText + ')'));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
},
|
||||
|
||||
load: function(files, callback) {
|
||||
var deferred = {};
|
||||
deferred.promise = new Promise(resolve => {
|
||||
deferred.resolve = resolve;
|
||||
});
|
||||
|
||||
if (!Array.isArray(files)) {
|
||||
files = [files];
|
||||
}
|
||||
|
||||
var loadsRemaining = files.length, self = this;
|
||||
function perFileCallback(file) {
|
||||
if (self._isLoading[file]) {
|
||||
delete self._isLoading[file];
|
||||
}
|
||||
self._loaded[file] = true;
|
||||
|
||||
if (--loadsRemaining === 0) {
|
||||
deferred.resolve();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
|
||||
if (this._loaded[file.id || file]) {
|
||||
perFileCallback(file);
|
||||
} else if (this._isLoading[file]) {
|
||||
this._isLoading[file].addEventListener(
|
||||
'load', perFileCallback.bind(null, file));
|
||||
} else {
|
||||
var method, idx;
|
||||
if (typeof file === 'string') {
|
||||
method = file.match(/\.([^.]+)$/)[1];
|
||||
idx = file;
|
||||
} else {
|
||||
method = 'html';
|
||||
idx = file.id;
|
||||
}
|
||||
|
||||
this['_' + method](file, perFileCallback.bind(null, idx));
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
return new LazyLoader();
|
||||
}());
|
||||
135
assets/webapps/uitest.gaiamobile.org/shared/js/mime_mapper.js
Normal file
135
assets/webapps/uitest.gaiamobile.org/shared/js/mime_mapper.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/* exported MimeMapper */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* MimeMapper helps gaia apps to decide the mapping of mimetype and extension.
|
||||
* The use cases often happen when apps need to know about the exact
|
||||
* mimetypes or extensions, such as to delegate the open web activity, we must
|
||||
* have suitable mimetypes or extensions to request the right activity
|
||||
*
|
||||
* The mapping is basically created according to:
|
||||
* http://en.wikipedia.org/wiki/Internet_media_type
|
||||
*
|
||||
* The supported formats are considered base on the deviceStorage properties:
|
||||
* http://dxr.mozilla.org/mozilla-central/toolkit/content/
|
||||
* devicestorage.properties
|
||||
*
|
||||
*/
|
||||
|
||||
var MimeMapper = {
|
||||
// This list only contains the extensions we currently supported
|
||||
// We should make it more complete for further usages
|
||||
_typeToExtensionMap: {
|
||||
// Image
|
||||
'image/jpeg': 'jpg',
|
||||
'image/png': 'png',
|
||||
'image/gif': 'gif',
|
||||
'image/bmp': 'bmp',
|
||||
// Audio
|
||||
'audio/mpeg': 'mp3',
|
||||
'audio/mp4': 'm4a',
|
||||
'audio/ogg': 'ogg',
|
||||
'audio/webm': 'webm',
|
||||
'audio/3gpp': '3gp',
|
||||
'audio/amr': 'amr',
|
||||
// Video
|
||||
'video/mp4': 'mp4',
|
||||
'video/mpeg': 'mpg',
|
||||
'video/ogg': 'ogg',
|
||||
'video/webm': 'webm',
|
||||
'video/3gpp': '3gp',
|
||||
'video/3gpp2': '3g2',
|
||||
// Application
|
||||
// If we want to support some types, like pdf, just add
|
||||
'application/pdf': 'pdf',
|
||||
'application/vcard': 'vcf',
|
||||
// Text
|
||||
'text/vcard': 'vcf',
|
||||
'text/x-vcard': 'vcf'
|
||||
},
|
||||
|
||||
// This list only contains the mimetypes we currently supported
|
||||
// We should make it more complete for further usages
|
||||
_extensionToTypeMap: {
|
||||
// Image
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'jpe': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'bmp': 'image/bmp',
|
||||
// Audio
|
||||
'mp3': 'audio/mpeg',
|
||||
'm4a': 'audio/mp4',
|
||||
'm4b': 'audio/mp4',
|
||||
'm4p': 'audio/mp4',
|
||||
'm4r': 'audio/mp4',
|
||||
'aac': 'audio/aac',
|
||||
'opus': 'audio/ogg',
|
||||
'amr': 'audio/amr',
|
||||
// Video
|
||||
'mp4': 'video/mp4',
|
||||
'mpeg': 'video/mpeg',
|
||||
'mpg': 'video/mpeg',
|
||||
'ogv': 'video/ogg',
|
||||
'ogx': 'video/ogg',
|
||||
'webm': 'video/webm',
|
||||
'3gp': 'video/3gpp',
|
||||
'3g2': 'video/3gpp2',
|
||||
'ogg': 'video/ogg',
|
||||
// Application
|
||||
// If we want to support some extensions, like pdf, just add
|
||||
'pdf': 'application/pdf',
|
||||
// Text
|
||||
'vcf': 'text/vcard'
|
||||
},
|
||||
_parseExtension: function(filename) {
|
||||
var array = filename.split('.');
|
||||
return array.length > 1 ? array.pop() : '';
|
||||
},
|
||||
|
||||
isSupportedType: function(mimetype) {
|
||||
return (mimetype in this._typeToExtensionMap);
|
||||
},
|
||||
|
||||
isSupportedExtension: function(extension) {
|
||||
return (extension in this._extensionToTypeMap);
|
||||
},
|
||||
|
||||
isFilenameMatchesType: function(filename, mimetype) {
|
||||
var extension = this._parseExtension(filename);
|
||||
var guessedType = this.guessTypeFromExtension(extension);
|
||||
return (guessedType == mimetype);
|
||||
},
|
||||
|
||||
guessExtensionFromType: function(mimetype) {
|
||||
return this._typeToExtensionMap[mimetype];
|
||||
},
|
||||
|
||||
guessTypeFromExtension: function(extension) {
|
||||
return this._extensionToTypeMap[extension];
|
||||
},
|
||||
|
||||
// If mimetype is not in the supported list, we will try to
|
||||
// predict the possible valid mimetype based on extension.
|
||||
guessTypeFromFileProperties: function(filename, mimetype) {
|
||||
var extension = this._parseExtension(filename);
|
||||
var type = this.isSupportedType(mimetype) ?
|
||||
mimetype : this.guessTypeFromExtension(extension);
|
||||
return type || '';
|
||||
},
|
||||
|
||||
// if mimetype is not supported, preserve the original extension
|
||||
// and add the predict result as new extension.
|
||||
// If both filename and mimetype are not supported, return the original
|
||||
// filename.
|
||||
ensureFilenameMatchesType: function(filename, mimetype) {
|
||||
if (!this.isFilenameMatchesType(filename, mimetype)) {
|
||||
var guessedExt = this.guessExtensionFromType(mimetype);
|
||||
if (guessedExt) {
|
||||
filename += '.' + guessedExt;
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* exported SettingsListener */
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = {
|
||||
/* lock stores here */
|
||||
_lock: null,
|
||||
|
||||
/* keep record of observers in order to remove them in the future */
|
||||
_observers: [],
|
||||
|
||||
/**
|
||||
* getSettingsLock: create a lock or retrieve one that we saved.
|
||||
* mozSettings.createLock() is expensive and lock should be reused
|
||||
* whenever possible.
|
||||
*/
|
||||
getSettingsLock: function sl_getSettingsLock() {
|
||||
// If there is a lock present we return that
|
||||
if (this._lock && !this._lock.closed) {
|
||||
return this._lock;
|
||||
}
|
||||
|
||||
// If there isn't we return one.
|
||||
var settings = window.navigator.mozSettings;
|
||||
|
||||
return (this._lock = settings.createLock());
|
||||
},
|
||||
|
||||
observe: function sl_observe(name, defaultValue, callback) {
|
||||
var settings = window.navigator.mozSettings;
|
||||
if (!settings) {
|
||||
window.setTimeout(function() { callback(defaultValue); });
|
||||
return;
|
||||
}
|
||||
|
||||
var req;
|
||||
try {
|
||||
req = this.getSettingsLock().get(name);
|
||||
} catch (e) {
|
||||
// It is possible (but rare) for getSettingsLock() to return
|
||||
// a SettingsLock object that is no longer valid.
|
||||
// Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
|
||||
// is fixed, we just catch the resulting exception and try
|
||||
// again with a fresh lock
|
||||
console.warn('Stale lock in settings_listener.js.',
|
||||
'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
|
||||
this._lock = null;
|
||||
req = this.getSettingsLock().get(name);
|
||||
}
|
||||
|
||||
req.addEventListener('success', (function onsuccess() {
|
||||
callback(typeof(req.result[name]) != 'undefined' ?
|
||||
req.result[name] : defaultValue);
|
||||
}));
|
||||
|
||||
var settingChanged = function settingChanged(evt) {
|
||||
callback(evt.settingValue);
|
||||
};
|
||||
settings.addObserver(name, settingChanged);
|
||||
this._observers.push({
|
||||
name: name,
|
||||
callback: callback,
|
||||
observer: settingChanged
|
||||
});
|
||||
},
|
||||
|
||||
unobserve: function sl_unobserve(name, callback) {
|
||||
var settings = window.navigator.mozSettings;
|
||||
var that = this;
|
||||
this._observers.forEach(function(value, index) {
|
||||
if (value.name === name && value.callback === callback) {
|
||||
settings.removeObserver(name, value.observer);
|
||||
that._observers.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* exported SettingsURL */
|
||||
'use strict';
|
||||
|
||||
// This library helps with managing settings that can either send back
|
||||
// string uris or blobs that need to be turned into blob: uris.
|
||||
// Usage is as follows:
|
||||
// var blobURL = new SettingsURL();
|
||||
// element.src = blobURL.set(settingValue);
|
||||
|
||||
function SettingsURL() {
|
||||
this._url = null;
|
||||
this._isBlob = false;
|
||||
}
|
||||
|
||||
SettingsURL.prototype = {
|
||||
set: function(value) {
|
||||
if (this._isBlob) {
|
||||
window.URL.revokeObjectURL(this._url);
|
||||
}
|
||||
if (value instanceof Blob) {
|
||||
this._isBlob = true;
|
||||
this._url = window.URL.createObjectURL(value);
|
||||
} else {
|
||||
this._isBlob = false;
|
||||
this._url = value;
|
||||
}
|
||||
return this._url;
|
||||
},
|
||||
|
||||
get: function() {
|
||||
return this._url;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue