android-sdk-client
Version:
Display list of available files of SDK repository
390 lines (329 loc) • 13.1 kB
JavaScript
;
const CONFIG = require('./package.json');
const fs = require('fs');
const url = require('url');
const path = require('path');
const process = require('process');
const Promise = require('es6-promise').Promise;
// http://www.html5rocks.com/en/tutorials/es6/promises/
const xml2js = require('xml2js');
const parser = new xml2js.Parser();
const axios = require('axios');
const mkdirp = require('mkdirp');
const humanize = require('humanize');
const EXPORT_PATH = path.join(process.cwd(), 'export');
const SYSTEM_IMAGE_NAMES = {
'armeabi-v7a': 'ARM EABI v7a System Image',
'arm64-v8a': 'ARM 64 v8a System Image',
'x86': 'Intel x86 Atom System Image',
'x86_64': 'Intel x86 Atom_64 System Image',
'mips': 'MIPS System Image'
};
var detailedList = false;
var exportFiles = false;
var showObsolete = false;
var repositories = [
{ url: 'https://dl.google.com/android/repository/repository-11.xml', name: 'SDK Repository' },
{ url: 'https://dl.google.com/android/repository/addons_list-2.xml', name: 'Add-ons List' }
];
var addons = [];
function getUrlFilename(urlStr) {
var parsed = url.parse(urlStr);
return parsed.pathname;
}
function parse(httpResult) {
return new Promise(function(resolve, reject) {
console.log('Parse XML: ', httpResult.config.url);
parser.parseString(httpResult.data, function (err, result) {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
}
/**
* Write out original XML and parsed JSON to file
*/
function exportSources(data, source) {
if (exportFiles) {
var outfileXml = path.join(EXPORT_PATH, getUrlFilename(source.url));
// replace file extension
var outfileJson = path.join(path.dirname(outfileXml), path.basename(outfileXml, path.extname(outfileXml)) + '.json');
mkdirp(path.dirname(outfileXml), function () {
fs.writeFile(outfileXml, source.xml.data);
fs.writeFile(outfileJson, JSON.stringify(source.data, null, ' '));
});
}
return source;
}
function getRevisionString(revision) {
if (!revision) return '';
if (!revision[0]) return '';
if (typeof revision[0] === 'string') {
return '' + parseInt(revision[0], 10);
}
let major = revision[0] && revision[0]['sdk:major'] && revision[0]['sdk:major'][0]
? parseInt(revision[0]['sdk:major'][0], 10) : 0;
let minor = revision[0] && revision[0]['sdk:minor'] && revision[0]['sdk:minor'][0]
? parseInt(revision[0]['sdk:minor'][0], 10) : 0;
let micro = revision[0] && revision[0]['sdk:micro'] && revision[0]['sdk:micro'][0]
? parseInt(revision[0]['sdk:micro'][0], 10) : 0;
let preview = revision[0] && revision[0]['sdk:preview'] && revision[0]['sdk:preview'][0]
? parseInt(revision[0]['sdk:preview'][0], 10) : 0;
if (!major) return '';
let out = major;
if (minor || micro) {
out += `.${minor}`;
}
if (micro !== 0) {
out += `.${micro}`;
}
if (preview) {
out += ` rc${preview}`;
}
return out;
}
function printData(data, baseUrl, type, name) {
try {
let obsoleteItem = !!data['sdk:obsolete'];
if (!showObsolete && obsoleteItem) {
return;
}
let obsoleteString = obsoleteItem ? ' (Obsolete)' : '';
let version = data['sdk:version'] ? data['sdk:version'][0] : '';
let sdkApiLevel = data['sdk:api-level'] ? data['sdk:api-level'][0] : '';
let sdkRevision = data['sdk:revision'] ? getRevisionString(data['sdk:revision']) : '';
let nameDisplay = data['sdk:name-display'] ? data['sdk:name-display'][0] : '';
let path = data['sdk:path'] ? data['sdk:path'][0] : '';
let abi = data['sdk:abi'] ? data['sdk:abi'][0] : '';
let description;
let extraDescription;
let pathDescription;
let requires;
if (data['sdk:description']) {
description = data['sdk:description'][0].trim();
} else if (type === 'Sample') {
description = 'Samples for SDK API ' + sdkApiLevel + ', revision ' + sdkRevision;
} else if (type === 'PlatformTool') {
description = 'Android SDK Platform-tools, revision ' + sdkRevision;
} else if (type === 'BuildTool') {
description = 'Android SDK Build-tools, revision ' + sdkRevision;
} else if (type === 'Tool') {
description = 'Android SDK Tools, revision ' + sdkRevision;
} else if (type === 'Doc') {
description = `Documentation for Android SDK, API ${sdkApiLevel}, revision ${sdkRevision}`;
} else if (type === 'Source') {
description = `Sources for Android SDK, API ${sdkApiLevel}, revision ${sdkRevision}`;
}
if (type === 'Addon') {
extraDescription = description;
description = `${nameDisplay}, Android API ${sdkApiLevel}, revision ${sdkRevision}`;
requires = `Requires SDK Platform Android API ${sdkApiLevel}`;
}
if (type === 'Extra') {
extraDescription = description;
description = `${nameDisplay}, revision ${sdkRevision}`;
pathDescription = `Install path: extras/android/${path}`;
}
if (type === 'SystemImage') {
extraDescription = SYSTEM_IMAGE_NAMES[abi] ? SYSTEM_IMAGE_NAMES[abi] : abi;
requires = `Requires SDK Platform Android API ${sdkApiLevel}`;
}
description += obsoleteString;
let vendorDisplay = data['sdk:vendor-display'] ? 'by ' + data['sdk:vendor-display'][0] : '';
if (detailedList) {
console.log('------------');
console.log(' Type: ' + type);
console.log(' Desc: ' + description);
if (version) {
console.log(' Version ' + version);
}
if (sdkApiLevel) {
console.log(' API ' + sdkApiLevel);
}
if (sdkRevision) {
console.log(' Revision ' + sdkRevision);
}
if (vendorDisplay) {
console.log(' ' + vendorDisplay);
}
if (extraDescription) {
console.log(' ' + extraDescription);
}
if (pathDescription) {
console.log(' ' + pathDescription);
}
if (requires) {
console.log(' ' + requires);
}
data['sdk:archives'][0]['sdk:archive'].forEach(arch => {
let os = arch['sdk:host-os'] ? arch['sdk:host-os'][0] : 'download';
let downloadUrl = url.resolve(baseUrl, arch['sdk:url'][0]);
let size = parseInt(arch['sdk:size'][0], 10);
let checksum = arch['sdk:checksum'][0]._;
let checksumType = arch['sdk:checksum'][0].$.type;
console.log(new Array(9 - os.length).join(' ') + os + ': ' + downloadUrl);
console.log(' size: ' + humanize.filesize(size));
console.log(' ' + checksumType + ': ' + checksum);
});
} else {
let out = ` - ${name}, revision ${getRevisionString(data['sdk:revision'])}${obsoleteString}`;
console.log(out);
}
} catch(e) {
console.error(e);
}
}
function printDetails(data, source) {
try {
if (source.data['sdk:sdk-repository']) {
if (source.data['sdk:sdk-repository']['sdk:platform']) {
source.data['sdk:sdk-repository']['sdk:platform'].forEach(item => {
let version = item['sdk:version'] ? item['sdk:version'][0] : '';
printData(item, source.url, 'Platform', `SDK Platform Android ${version}`);
});
}
if (source.data['sdk:sdk-repository']['sdk:sample']) {
source.data['sdk:sdk-repository']['sdk:sample'].forEach(item => {
let sdkApiLevel = item['sdk:api-level'] ? item['sdk:api-level'][0] : '';
printData(item, source.url, 'Sample', `Samples for SDK API ${sdkApiLevel}`);
});
}
if (source.data['sdk:sdk-repository']['sdk:platform-tool']) {
source.data['sdk:sdk-repository']['sdk:platform-tool'].forEach(item => {
printData(item, source.url, 'PlatformTool', 'Android SDK Platform-tools');
});
}
if (source.data['sdk:sdk-repository']['sdk:build-tool']) {
source.data['sdk:sdk-repository']['sdk:build-tool'].forEach(item => {
printData(item, source.url, 'BuildTool', 'Android SDK Build-tools');
});
if (source.data['sdk:sdk-repository']['sdk:tool']) {
source.data['sdk:sdk-repository']['sdk:tool'].forEach(item => {
printData(item, source.url, 'Tool', 'Android SDK Tools');
});
}
if (source.data['sdk:sdk-repository']['sdk:doc']) {
source.data['sdk:sdk-repository']['sdk:doc'].forEach(item => {
let sdkApiLevel = item['sdk:api-level'] ? item['sdk:api-level'][0] : '';
printData(item, source.url, 'Doc', `Documentation for Android SDK, API ${sdkApiLevel}`);
});
}
if (source.data['sdk:sdk-repository']['sdk:source']) {
source.data['sdk:sdk-repository']['sdk:source'].forEach(item => {
let sdkApiLevel = item['sdk:api-level'] ? item['sdk:api-level'][0] : '';
printData(item, source.url, 'Source', `Sources for Android SDK, API ${sdkApiLevel}`);
});
}
}
}
if (source.data['sdk:sdk-addon']) {
if (source.data['sdk:sdk-addon']['sdk:add-on']) {
source.data['sdk:sdk-addon']['sdk:add-on'].forEach(item => {
let nameDisplay = item['sdk:name-display'] ? item['sdk:name-display'][0] : '';
let sdkApiLevel = item['sdk:api-level'] ? item['sdk:api-level'][0] : '';
printData(item, source.url, 'Addon', `${nameDisplay}, Android API ${sdkApiLevel}`);
});
}
if (source.data['sdk:sdk-addon']['sdk:extra']) {
source.data['sdk:sdk-addon']['sdk:extra'].forEach(item => {
let nameDisplay = item['sdk:name-display'] ? item['sdk:name-display'][0] : '';
printData(item, source.url, 'Extra', nameDisplay);
});
}
}
if (source.data['sdk:sdk-sys-img']) {
if (source.data['sdk:sdk-sys-img']['sdk:system-image']) {
source.data['sdk:sdk-sys-img']['sdk:system-image'].forEach(item => {
let abi = item['sdk:abi'] ? item['sdk:abi'][0] : '';
let abiName = SYSTEM_IMAGE_NAMES[abi] ? SYSTEM_IMAGE_NAMES[abi] : abi;
let sdkApiLevel = item['sdk:api-level'] ? item['sdk:api-level'][0] : '';
printData(item, source.url, 'SystemImage', `${abiName}, Android API ${sdkApiLevel}`);
});
}
}
} catch(e) {
console.error(e, data, source);
}
return source;
}
function getAndParse(source) {
return new Promise(function(resolve, reject) {
console.log('Fetching URL:', source.url);
axios.get(source.url)
.then(data => { source.xml = data; return data; })
.then(parse)
.then(data => { source.data = data; return data; })
.then(data => exportSources(data, source))
// .then(data => printDetails(data, source))
.then(resolve)
.catch(reject);
});
}
function getData() {
getRepositoryData()
.then(() => getAddonsData())
.catch(err => console.log(err))
.then(() => { console.log('Done loading packages.'); printList(); });
}
function getRepositoryData() {
return new Promise(function(resolve, reject) {
var list = [];
repositories.forEach(source => list.push(getAndParse(source)));
Promise.all(list).then(() => {
// fill addons list
try {
repositories[1].data['sdk:sdk-addons-list']['sdk:addon-site']
.forEach(addon => addons.push({
url: url.resolve(repositories[1].url, addon['sdk:url'][0]),
name: addon['sdk:name'][0]
}));
} catch (e) {
console.log(e);
}
try {
repositories[1].data['sdk:sdk-addons-list']['sdk:sys-img-site']
.forEach(addon => addons.push({
url: url.resolve(repositories[1].url, addon['sdk:url'][0]),
name: addon['sdk:name'][0]
}));
} catch (e) {
console.log(e);
}
resolve();
}, reject);
});
}
function getAddonsData() {
return new Promise(function(resolve, reject) {
var list = [];
addons.forEach(source => list.push(getAndParse(source)));
Promise.all(list).then(resolve, reject);
});
}
function printList() {
repositories.forEach(repo => {
printDetails(null, repo);
});
addons.forEach(repo => {
printDetails(null, repo);
});
}
// http://www.2ality.com/2016/04/unhandled-rejections.html
process.on('unhandledRejection', (reason) => {
console.log('unhandledRejection');
console.log('Reason: ' + reason);
});
module.exports = function(options) {
if (options.v || options.version) {
console.log(CONFIG.version);
return;
}
detailedList = options.e || options.extended;
exportFiles = options.s || options.save;
showObsolete = options.o || options.obsolete;
getData();
};