var logger = Logger('package.js');
var $tree = $('#tic-package-left-col-tree');
- var packages = [];
var repos = [];
+ var packages = [];
+ var pkgInfo = null;
+ var provides = null;
+ var files = null;
+ var groups = null;
+ var groupId = 0;
+
+ if (!Date.now) { Date.now = function now() {
+ return new Date().getTime();
+ };
+ }
function _getChecked() {
- return _.filter(packages, ['state.checked', true]);
+ var checkedList = [];
+ _.forEach(pkgInfo, function(value, key){
+ if(value.view[0].state.checked === true){
+ checkedList.push(value);
+ }
+ });
+ return checkedList;
}
function _find(name, data) {
if (_.isEmpty(data)) {
- data = packages;
+ data = pkgInfo;
}
- return _.find(data, {'text' : name});
+ return data[name];
}
function _findTreeNode(name) {
}
function _setDefaultPackage(defaultPackages) {
- var nodes = _.filter(packages, function (node) {
- return _.includes(defaultPackages, node.text);
+ var nodes = []
+ var pickDefault = _.pick(pkgInfo, defaultPackages);
+ _.forEach(pickDefault, function(value, key){
+ // One or more of the same nodes can exist
+ _.forEach(value.view, function(node){
+ nodes.push(node);
+ });
});
+
$tree.treeview('checkNode', [nodes, { silent: true }]);
_.forEach(nodes, function(node) {
node.state.checked = true;
});
}
+ // Compares two rpm version numbers (e.g. "1.7.1" or "1.2b").
+ // This function is based on https://gist.github.com/em92/d58944f21c68b69433cefb6c49e0defd
+ function versionCompare(v1, v2, options) {
+ if(!v1 && !v2){
+ return 0;
+ } else if(!v2){
+ return 1;
+ } else if(!v1){
+ return -1;
+ }
+
+ var v1parts = v1.split(/[.+-/_]/);
+ var v2parts = v2.split(/[.+-/_]/);
+
+ function compareParts(v1parts, v2parts, options) {
+ var zeroExtend = options && options.zeroExtend;
+
+ if (zeroExtend) {
+ while (v1parts.length < v2parts.length) v1parts.push("0");
+ while (v2parts.length < v1parts.length) v2parts.push("0");
+ }
+
+ for (var i = 0; i < v1parts.length; ++i) {
+ if (v2parts.length == i) {
+ return 1;
+ }
+
+ var v1part = parseInt(v1parts[i]);
+ var v2part = parseInt(v2parts[i]);
+ // (NaN == NaN) -> false
+ var v1part_is_string = !(v1part == v1part);
+ var v2part_is_string = !(v2part == v2part);
+ v1part = v1part_is_string ? v1parts[i] : v1part;
+ v2part = v2part_is_string ? v2parts[i] : v2part;
+
+ if (v1part_is_string == v2part_is_string) {
+ if (v1part_is_string == false) {
+ // integer compare
+ if (v1part == v2part) {
+ continue;
+ } else if (v1part > v2part) {
+ return 1;
+ } else {
+ return -1;
+ }
+ } else {
+ // letters and numbers in string
+ // split letters and numbers
+ var v1subparts = v1part.match(/[a-zA-Z]+|[0-9]+/g);
+ var v2subparts = v2part.match(/[a-zA-Z]+|[0-9]+/g);
+ if ( (v1subparts.length == 1) && (v2subparts.length == 1) ) {
+ // only letters in string
+ v1part = v1subparts[0];
+ v2part = v2subparts[0];
+ if (v1part == v2part) {
+ continue;
+ } else if (v1part > v2part) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ var result = compareParts(v1subparts, v2subparts);
+ if (result == 0) {
+ continue;
+ } else {
+ return result;
+ }
+ }
+ } else {
+ return v2part_is_string ? 1 : -1;
+ }
+ }
+
+ if (v1parts.length != v2parts.length) {
+ return -1;
+ }
+ return 0;
+ }
+
+ return compareParts(v1parts, v2parts, options);
+ }
+
+
+
+ // analyze install-dependency (requires)
+ function _analyzeInstallDependency(checkNode) {
+ var stack = [];
+ var pkg_count = Object.keys(pkgInfo).length
+ var number = 0;
+ var sccNum = 0;
+ var groupNum = 0;
+ var selected = new Array(pkg_count);
+ var scc_id = new Array(pkg_count);
+ var min_num = new Array(pkg_count);
+
+ // init check type
+ _.forEach(pkgInfo, function(value, key){
+ // check type: -1(unchecked), 0(checked), 1~N(checking)
+ selected[value.id] = value.view[0].state.checked ? 0 : -1;
+ scc_id[value.id] = 0;
+ });
+
+ function _comapre_ver(ver1, ver2, flag){
+ var epoch1 = ver1.epoch;
+ var epoch2 = ver2.epoch;
+ // epoch is a number (optional, default=0)
+ epoch1 = epoch1 ? epoch1 : 0;
+ epoch2 = epoch2 ? epoch2 : 0;
+
+ if (epoch1 == epoch2){
+ var result = versionCompare(ver1.ver, ver2.ver);
+ // version match (ver1 == ver2)
+ if(result == 0){
+ // flag == true means to compare versions between require and provide
+ if(!flag || ver1.rel){
+ return versionCompare(ver1.rel, ver2.rel);
+ }
+ return result;
+ }
+ return result;
+ } else if(epoch1 > epoch2){
+ return 1
+ } else {
+ return -1;
+ }
+ }
+
+ function _makeSCC(pkgId){
+ sccNum += 1;
+ var sccList = [];
+
+ // make scc
+ while(!_.isEmpty(stack)){
+ var pkg = stack.pop();
+ scc_id[pkg.id] = sccNum;
+ sccList.push(pkg);
+
+ if(pkg.id === pkgId)
+ break;
+ }
+
+ // circular depependency
+ if (sccList.length > 1){
+ groupId += 1;
+ var groupPkgList = [];
+ _.forEach(sccList, function(pkg){
+ pkg.group = groupId;
+ groupPkgList.push(pkg.name);
+ });
+ groups[groupId] = groupPkgList;
+ logger.info('circular dependency: groupId(' + groupId + '), list(' + groupPkgList + ')');
+ }
+ }
+
+ function _createReference(node1, node2){
+ if(!_.isEmpty(node1.forward) && _.includes(node1.forward, node2.name))
+ return
+
+ if(!_.isEmpty(node1.forward)){
+ node1.forward.push(node2.name);
+ } else {
+ node1.forward = [node2.name];
+ }
+
+ if(!_.isEmpty(node2.backward)){
+ node2.backward.push(node1.name);
+ } else {
+ node2.backward = [node1.name];
+ }
+ }
+
+ function _select_rpm(capability, require){
+ if(capability.length == 1){
+ return pkgInfo[capability[0].name];
+ }
+
+ var provideList = [];
+ // 1. Choose the rpm included in version from provides
+ if (require.ver){
+ _.forEach(capability, function(provide){
+ var cmpResult = _comapre_ver(require, provide.data, true);
+ if (cmpResult === 0 && _.includes(['EQ', 'GE', 'LE'], require.flags)){
+ provideList.push(provide);
+ } else if(cmpResult === 1 && _.includes(['LT', 'LE'], require.flags)){
+ provideList.push(provide);
+ } else if(cmpResult === -1 && _.includes(['GT', 'GE'], require.flags)){
+ provideList.push(provide);
+ }
+ });
+ } else {
+ provideList = capability;
+ }
+
+ // error case (the rpm does not exist)
+ if (_.isEmpty(provideList)){
+ return null;
+ }
+
+ if (provideList.length == 1){
+ return pkgInfo[provideList[0].name];
+ }
+
+ // 2 Select one of the rpms by priority
+ // 2-1. Choose the default rpm or the selected rpm
+ // TODO: default profile rpm should be selected
+ _.forEach(provideList, function(provide){
+ var tmpInfo = pkgInfo[provide.name];
+ if(tmpInfo.view[0].state.checked || selected[tmpInfo.id] >= 1){
+ return tmpInfo;
+ }
+ });
+
+ var maxVersion = null;
+ // # 2-2. Select the latest version of rpm
+ _.forEach(provideList, function(provide){
+ if(maxVersion){
+ var ret = _comapre_ver(maxVersion.data, provide.data)
+ if(ret == -1){
+ maxVersion = provide;
+ }
+ } else {
+ maxVersion = provide;
+ }
+ });
+
+ return pkgInfo[maxVersion.name];
+ }
+
+ function _analyzeDep(node){
+ var pkgId = node.id;
+ number += 1;
+ selected[pkgId] = number;
+ min_num[pkgId] = number;
+ stack.push(node);
+
+ var dependents = {};
+ dependents[node.name] = node;
+
+ // installation dependency analysis of package
+ // TODO: recommends
+ _.forEach(['requires'], function(depTag){
+ if(_.has(node, depTag)){
+ _.forEach(node.requires, function(require){
+ var choose = null;
+
+ if(_.has(provides, require.name)){
+ var capList = provides[require.name];
+ choose = _select_rpm(capList, require);
+ } else if(_.has(files, require.name)){
+ choose = pkgInfo[files[require.name][0]];
+ }
+
+ if (choose){
+ // add forward/backward reference
+ _createReference(node, choose);
+
+ if(selected[choose.id] == -1){
+ var result = _analyzeDep(choose);
+ _.forEach(result, function(value, key){
+ if(!_.has(dependents, key)){
+ dependents[key] = value;
+ }
+ });
+ min_num[pkgId] = Math.min(min_num[pkgId], min_num[choose.id])
+ } else if(selected[choose.id] >= 1 && scc_id[choose.id] == 0){
+ // cross edge that can not be ignored
+ min_num[pkgId] = Math.min(min_num[pkgId], min_num[choose.id])
+ }
+ } else {
+ logger.info('the capability('+require.name+')does not exist. should be checked for error');
+ }
+ });
+ }
+ });
+
+ if (min_num[pkgId] == selected[pkgId]){
+ _makeSCC(pkgId);
+ }
+
+ return dependents;
+ }
+
+ return _analyzeDep(checkNode);
+ }
+
+ // analyze uncheck dependency
+ function _analyzeUncheckDependency(uncheckNode) {
+
+ function _checkCircularDependency(node){
+ var groupId = node.group
+ var groupPkgList = groups[groupId];
+ var groupObj = {};
+
+ // Set object for group
+ _.forEach(groupPkgList, function(pkgName){
+ groupObj[pkgName] = null;
+ });
+
+ var isUncheckable = true;
+ _.forEach(groupPkgList, function(pkgName) {
+ var pkg = _find(pkgName);
+
+ // the node is selfChecked or uncheckable
+ if(pkg.selfChecked || !isUncheckable){
+ isUncheckable = false;
+ return false;
+ }
+
+ _.forEach(pkg.backward, function(backRef) {
+ // If node is Referenced by another node (Not a node in the group),
+ // unable to uncheck group nodes
+ if(!_.has(groupObj, backRef)){
+ isUncheckable = false;
+ return false;
+ }
+ });
+ });
+
+ if(isUncheckable){
+ // init visited obj
+ groupVisited[groupId] = {};
+
+ // Delete backward/forward reference of group node
+ _.forEach(groupPkgList, function(pkgName) {
+ var pkg = _find(pkgName);
+ // backward ref.
+ pkg.backward = null;
+ // visited init
+ groupVisited[groupId][pkg.name] = -1;
+ });
+ logger.info('Group(' + groupId + ') is uncheckable : ' + groupPkgList);
+ return true;
+ } else {
+ logger.info('Group(' + groupId + ') is Not uncheckable: ' + groupPkgList);
+ return false;
+ }
+ }
+
+ function _analyzeUncheck(parent, node){
+ if(!_.isEmpty(parent)){
+ // TODO: performance
+ if(!_.isEmpty(node.backward)){
+ var bIndex = node.backward.indexOf(parent.name);
+ if(bIndex > -1){
+ // TODO: performance
+ // remove backward reference (parent)
+ node.backward.splice(bIndex, 1);
+ }
+ }
+ // selfCheck node is not unchecked
+ if (node.selfChecked == true)
+ return null;
+ }
+
+ var uncheckPkgs = null;
+ // Check that the selected node is uncheckable
+ if(!_.isEmpty(node.backward)){
+ // check circular dependency
+ if(!node.group || !_checkCircularDependency(node)){
+ return null;
+ }
+ }
+
+ // the selected node is uncheckable
+ uncheckPkgs = {};
+ uncheckPkgs[node.name] = node;
+ // uncheckable pkg of group
+ if(node.group && !_.isEmpty(groupVisited[node.group])){
+ groupVisited[node.group][node.name] = 1;
+ }
+
+ // if selected node has forward references
+ if(!_.isEmpty(node.forward)){
+ _.forEach(node.forward, function(fname){
+ var forwardNode = _find(fname);
+
+ // If pkg has a circular dependency and is unchekcable,
+ // circular dep. pkgs can only be visited once
+ var fvisit = groupVisited[forwardNode.group];
+ if(!_.isEmpty(fvisit) && fvisit[fname] === 1){
+ return; // continue;
+ }
+
+ var result = _analyzeUncheck(node, forwardNode);
+
+ // updates pkgs for uncheck
+ if(!_.isEmpty(result)){
+ _.forEach(result, function(value, key){
+ if(!_.has(uncheckPkgs, key)){
+ uncheckPkgs[key] = value;
+ }
+ });
+ }
+ });
+ // forward reference reset
+ node.forward = null;
+ node.group = null;
+ }
+
+ return uncheckPkgs;
+ }
+
+ // { 'group_id' : {pkgName: -1, ... }, ... }
+ var groupVisited = {};
+ var uncheckPkgs = _analyzeUncheck(null, uncheckNode);
+
+ // delete groupId from groups
+ _.forEach(groupVisited, function(groupList, groupId){
+ if(!_.isEmpty(groups[groupId])){
+ delete groups[groupId];
+ }
+ });
+
+ return uncheckPkgs;
+ }
+
/**
* Summary page
*/
var list = _getChecked();
var count = _.size(list);
+
var imageSize = _.sumBy(list, function getImageSize(item) {
return _.toNumber(item.size || 0);
});
packageListBadge.html(count);
}
if (!_.isEmpty(list)) {
- packageList.html(_.orderBy(_.map(list, 'text')).join('<br>'));
+ packageList.html(_.orderBy(_.map(list, 'name')).join('<br>'));
}
$('#tic-package-create').toggleClass('disabled', count === 0);
var dependency = $('#tic-package-info-dependency').empty();
var dependencyBadge = $('#tic-package-info-dependency-badge').empty();
- if (!_.isEmpty(node.text)) {
- text.html(node.text);
- }
- if (!_.isEmpty(node.version)) {
- version.html(node.version);
+ var pkg = pkgInfo[node.text];
+
+ if (!_.isEmpty(pkg.name)) {
+ text.html(pkg.name);
}
- if (!_.isEmpty(node.arch)) {
- arch.html(node.arch);
+ if (!_.isEmpty(pkg.version)) {
+ if(!_.isEmpty(pkg.version.rel)){
+ version.html(pkg.version.ver + '-' + pkg.version.rel);
+ } else {
+ version.html(pkg.version.ver);
+ }
}
- if (!_.isEmpty(node.size)) {
- size.html(Util.bytesToSize(node.size));
+ if (!_.isEmpty(pkg.arch)) {
+ arch.html(pkg.arch);
}
- if (!_.isEmpty(node.installed)) {
- installedSize.html(Util.bytesToSize(node.installed));
+ if (!_.isEmpty(pkg.size)) {
+ size.html(Util.bytesToSize(pkg.size));
}
- if (!_.isEmpty(node.summary)) {
- summary.html(node.summary);
+ if (!_.isEmpty(pkg.installed)) {
+ installedSize.html(Util.bytesToSize(pkg.installed));
}
- if (!_.isEmpty(node.description)) {
- description.html(node.description);
+ if (!_.isEmpty(pkg.summary)) {
+ summary.html(pkg.summary);
}
- if (!_.isEmpty(node.dependency)) {
- dependencyBadge.html(node.dependency.length)
- dependency.html(_.orderBy(node.dependency).join('<br>'));
+ if (!_.isEmpty(pkg.description)) {
+ description.html(pkg.description);
}
+ // if (!_.isEmpty(info.dependency)) {
+ // dependencyBadge.html(info.dependency.length)
+ // dependency.html(_.orderBy(info.dependency).join('<br>'));
+ // }
}
/**
* Treeview: A node is checked.
*/
function _onNodeChecked(event, node) {
- if (_find(node.text).state.checked === true) {
+ var startTS = Date.now();
+ var localNode = _find(node.text);
+ if (localNode.view[0].state.checked === true) {
$tree.treeview('uncheckNode', [node, { silent: false }]);
return;
}
- logger.info('checked: ' + node.text);
+ logger.info('checked: ' + localNode.name);
_nodeSelected(event, node);
-
- var localNode = _find(node.text);
localNode.selfChecked = true;
- localNode.state.checked = true;
+ localNode.view[0].state.checked = true;
+
+ // analyze install-dependency (requires)
+ var depPkg = _analyzeInstallDependency(localNode)
+
+ var analyzeTS = Date.now();
+
+ // TODO: temporary code
+ var tempcode = [];
+ _.forEach(depPkg, function(value, key){ tempcode.push(value.name); });
+ logger.info(localNode.name + ' install-dependency(' + tempcode.length +'): ' + tempcode);
var toggleNode = [];
- if (!_.isEmpty(localNode.dependency)) {
- _.forEach(localNode.dependency, function(name) { // FIXME: Performance
- var depNode = _find(name);
- if (_.isEmpty(depNode)) {
- Util.showAlertDialog('Can not find dependency package. \'' + name + '\'');
- } else {
- if (depNode.state.checked === false) {
- toggleNode.push(depNode);
+ if (!_.isEmpty(depPkg)) {
+ _.forEach(depPkg, function(value, key) {
+ _.forEach(value.view, function(node) {
+ if (node.state.checked === false) {
+ toggleNode.push(node);
}
- }
+ });
});
- $tree.treeview('checkNode', [toggleNode, { silent: true }]);
+ $tree.treeview('checkNode', [toggleNode, { silent: true }]);
// update local data
_.forEach(toggleNode, function(node) {
node.state.checked = true;
});
_updateSummary();
+
+ var endTS = Date.now();
+ logger.info('[Check] Total time: ' + (endTS - startTS) + 'ms');
+ logger.info('[Check] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
+ logger.info('[Check] Update view time: ' + (endTS - analyzeTS) + 'ms');
}
}
* Treeview: A node is unchecked.
*/
function _onNodeUnchecked(event, node) {
- if (_find(node.text).state.checked === false) {
+ var startTS = Date.now();
+ var localNode = _find(node.text);
+ if (localNode.view[0].state.checked === false) {
$tree.treeview('checkNode', [node, { silent: false }]);
return;
}
logger.info('unchecked: ' + node.text);
- Util.showLoadingDialog(true);
- // TODO: Refactoring
- var localNode = _find(node.text);
+ var uncheckPkgs = _analyzeUncheckDependency(localNode);
- // check reference node
- var checkedRefNode = [];
- if (!_.isEmpty(localNode.reference)) {
- _.forEach(localNode.reference, function(refNode) {
- if (refNode.state.checked === true && !_.isEqual(refNode.text, localNode.text) && refNode.selfChecked === true) {
- checkedRefNode.push(refNode);
- }
- });
- if (!_.isEmpty(checkedRefNode)) {
- Util.showLoadingDialog(false);
- Util.showAlertDialog('\'' +localNode.text + '\'' + ' package repuired from ' + '\'' + _.toString(_.map(checkedRefNode, 'text')) + '\'');
+ var analyzeTS = Date.now();
- // cancel for unchecked
- $tree.treeview('checkNode', [node, { silent: true }]);
- localNode.state.checked = true;
- _updateSummary();
- return;
- }
- }
+ // TODO: temporary code
+ var tempcode = [];
+ _.forEach(uncheckPkgs, function(value, key){ tempcode.push(value.name); });
+ logger.info(localNode.name + ' uncheck-dependency(' + tempcode.length +'): ' + tempcode);
- // check dependency node
var toggleNode = [];
- if (!_.isEmpty(localNode.dependency) && localNode.selfChecked === true) {
- _.forEach(localNode.dependency, function(name) { // FIXME: Performance
- var depNode = _find(name);
- if (!_.isEqual(localNode.text, depNode.text)) {
- if (depNode.state.checked === true) {
- if (depNode.selfChecked === true) {
- logger.warn('\'' + depNode.text + '\'' + ' package was still checked because user checked item.');
- } else {
- toggleNode.push(depNode);
- }
+ if(!_.isEmpty(uncheckPkgs)) {
+ _.forEach(uncheckPkgs, function(value, key){
+ _.forEach(value.view, function(node){
+ if (node.state.checked === true) {
+ toggleNode.push(node);
}
- }
+ });
});
- // check reference of dependency node
- var checkedRefNode2 = [];
+ $tree.treeview('uncheckNode', [toggleNode, { silent: true }]);
+ // update local data
_.forEach(toggleNode, function(node) {
- if (!_.isEmpty(node.reference)) {
- checkedRefNode2.push(node);
- _.forEach(node.reference, function(refNode) {
- if (!_.isEqual(refNode.text, node.text) && !_.isEqual(refNode.text, localNode.text)) {
- if (refNode.state.checked === true && refNode.selfChecked === true) {
- checkedRefNode2.pop();
- return false;
- }
- }
- });
- }
- });
-
- $tree.treeview('uncheckNode', [checkedRefNode2, { silent: true }]);
- _.forEach(checkedRefNode2, function(node) {
node.state.checked = false;
});
+
+ //node.state.checked = false;
+ localNode.selfChecked = false;
+ _updateSummary();
+ } else {
+ Util.showAlertDialog('Could not uncheck the \'' +localNode.name + '\'' + '<br> because the \'' + localNode.backward + '\' packages depends on it');
+
+ // selected node change to check state
+ $tree.treeview('checkNode', [[node], { silent: true }]);
+ node.state.checked = true;
}
- localNode.state.checked = false;
- localNode.selfChecked = false;
- _updateSummary();
- Util.showLoadingDialog(false);
+ var endTS = Date.now();
+ logger.info('[Uncheck] Total time: ' + (endTS - startTS) + 'ms');
+ logger.info('[Uncheck] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
+ logger.info('[Uncheck] Update view time: ' + (endTS - analyzeTS) + 'ms');
}
/**
*/
function updatePackageTree(rawData) {
repos = rawData.repos;
+ pkgInfo = rawData.data.packages
+ provides = rawData.data.provides;
+ files = rawData.data.files;
+ groups = rawData.data.groups;
+
+ _.forEach(groups, function(value, key){
+ groupId += 1;
+ })
return new Promise(function (resolve, reject) {
function _onRendered(event, nodes) {
- packages = _.uniqBy(_.values(nodes), 'text');
- _.forEach(packages, function(node) {
- _.forEach(node.dependency, function(name) { // FIXME: Performance
- var depNode = _.find(packages, {'text' : name});
- depNode.selfChecked = false;
- if (_.isEmpty(depNode.reference)) {
- depNode.reference = [];
- }
- if (!_.isEqual(depNode.text, node.text)) {
- depNode.reference.push(node);
- }
- });
+ packages = _.values(nodes);
+ _.forEach(nodes, function(node, key) {
+ // add a reference variable for treeview
+ var pkg = pkgInfo[node.text]
+ if (pkg) {
+ if(_.isEmpty(pkg.view)){
+ pkg.view = [node];
+ } else {
+ pkg.view.push(node);
+ }
+ }
});
_setDefaultPackage(rawData.defaultpackages);
}
$tree.treeview({
- data: rawData.packages,
+ data: rawData.view,
showIcon: false,
showCheckbox: true,
onRendered: _onRendered,
return {
/**
* Initialize for treeview
- * @method updatePackageTree
+ * @method updatePackageTreepkginfo
* @param {array} array of objects
* @return Promise
*/