305 lines
7.5 KiB
JavaScript
305 lines
7.5 KiB
JavaScript
var path = require('path');
|
|
var System = require('./system');
|
|
var debug = require('./debug')('desktop-apps');
|
|
var Cache = require('./cache');
|
|
|
|
// Constants
|
|
var ICON_REALPATH_REGEX = /\..+$/;
|
|
var ICON_THEMES_ROOTDIR = '/usr/share/icons';
|
|
var PIXMAPS_ICONS_ROOTDIR = '/usr/share/pixmaps';
|
|
|
|
/**
|
|
* Find and load all the desktop files in the subdirectories of given dirs
|
|
*
|
|
* @param Array[String] rootDirs
|
|
* @return Promise
|
|
*/
|
|
exports.loadAllDesktopFiles = function(rootDirs) {
|
|
|
|
return exports.findAllDesktopFiles(rootDirs)
|
|
.then(function(filePaths) {
|
|
|
|
var promises = filePaths.map(function(path) {
|
|
return exports.loadDesktopFile(path);
|
|
});
|
|
|
|
return Promise.all(promises)
|
|
.then(function(contents) {
|
|
return contents.map(function(content, i) {
|
|
return { content: content, path: filePaths[i] };
|
|
});
|
|
})
|
|
;
|
|
|
|
})
|
|
;
|
|
|
|
};
|
|
|
|
/**
|
|
* Find all the desktop files in the subdirectories of given dirs
|
|
*
|
|
* @param Array[String] baseDirs
|
|
* @return Promise
|
|
*/
|
|
exports.findAllDesktopFiles = function(baseDirs) {
|
|
|
|
if(!Array.isArray(baseDirs)) {
|
|
baseDirs = [baseDirs];
|
|
}
|
|
|
|
var promises = baseDirs.map(function(baseDir) {
|
|
return System.findFiles('**/*.desktop', {cwd: baseDir, realpath: true});
|
|
});
|
|
|
|
return Promise.all(promises)
|
|
.then(function(apps) {
|
|
return uniq(flatten(apps));
|
|
})
|
|
;
|
|
|
|
};
|
|
|
|
/**
|
|
* Load a .desktop file ans return its parsed content
|
|
*
|
|
* @param string filePath
|
|
* @return Promise
|
|
*/
|
|
exports.loadDesktopFile = function(filePath) {
|
|
return System.loadINIFile(filePath);
|
|
};
|
|
|
|
var iconCache = new Cache();
|
|
|
|
/**
|
|
* Find the absolute path of a desktop icon
|
|
*
|
|
* @param string iconPath
|
|
* @return Promise
|
|
*/
|
|
exports.findIcon = function(iconName, themeName, size, themeIgnore) {
|
|
|
|
var cachedIcon = iconCache.get([iconName, themeName, size]);
|
|
|
|
if(cachedIcon) {
|
|
debug('Icon %s:%s:%s found in cache !', iconName, themeName, size);
|
|
return Promise.resolve(cachedIcon);
|
|
}
|
|
|
|
themeIgnore = themeIgnore || [];
|
|
if(themeIgnore.indexOf(themeIgnore) !== -1) {
|
|
debug('Theme %s already processed, ignoring...', themeName);
|
|
return Promise.resolve(null);
|
|
}
|
|
themeIgnore.push(themeName);
|
|
|
|
debug('Finding icon %s:%s:%s...', iconName, themeName, size);
|
|
|
|
if( ICON_REALPATH_REGEX.test(iconName) ) {
|
|
return Promise.resolve(iconName);
|
|
}
|
|
|
|
if(!themeName) {
|
|
return exports.findIconThemes()
|
|
.then(function(themes) {
|
|
themeIgnore = themeIgnore || [];
|
|
var promises = themes.map(function(theme) {
|
|
return exports.findIcon(iconName, theme, size, themeIgnore);
|
|
});
|
|
return Promise.all(promises)
|
|
.then(exports._selectBestIcon)
|
|
;
|
|
})
|
|
.then(_cacheIcon)
|
|
;
|
|
}
|
|
|
|
return exports.findClosestSizeIcon(iconName, themeName, size)
|
|
.then(function(foundIcon) {
|
|
|
|
if(foundIcon) return foundIcon;
|
|
|
|
debug('No icon found. Search in parents...');
|
|
|
|
return exports.findParentsThemeIcon(iconName, themeName, size, themeIgnore)
|
|
.then(function(iconPath) {
|
|
|
|
if(iconPath) return iconPath;
|
|
|
|
return exports.findPixmapsIcon(iconName);
|
|
|
|
})
|
|
;
|
|
|
|
})
|
|
.then(_cacheIcon)
|
|
;
|
|
|
|
|
|
function _cacheIcon(iconPath) {
|
|
iconCache.set([iconName, themeName, size], iconPath);
|
|
return iconPath;
|
|
}
|
|
|
|
};
|
|
|
|
exports.findParentsThemeIcon = function(iconName, themeName, size, themeIgnore) {
|
|
|
|
return exports.themeIndexExists(themeName)
|
|
.then(function(exists) {
|
|
|
|
if(!exists) return null;
|
|
|
|
return exports.loadThemeIndex(themeName)
|
|
.then(function(themeIndex) {
|
|
|
|
if(!themeIndex || !themeIndex['Icon Theme'].Inherits) return;
|
|
|
|
var parents = themeIndex['Icon Theme'].Inherits.split(',');
|
|
|
|
debug('Found parents %j', parents);
|
|
|
|
var promises = parents.map(function(themeName) {
|
|
return exports.findIcon(iconName, themeName, size, themeIgnore);
|
|
});
|
|
|
|
return Promise.all(promises)
|
|
.then(exports._selectBestIcon)
|
|
;
|
|
|
|
})
|
|
;
|
|
|
|
})
|
|
;
|
|
|
|
};
|
|
|
|
exports.findClosestSizeIcon = function(iconName, themeName, size) {
|
|
|
|
var themePath = path.join(ICON_THEMES_ROOTDIR, themeName);
|
|
|
|
var extPattern = '{svg,png}';
|
|
var filePattern = themeName+'/*/*/'+iconName+'.'+extPattern;
|
|
|
|
debug('File pattern %s', filePattern);
|
|
|
|
return System.findFiles(filePattern, {cwd: ICON_THEMES_ROOTDIR})
|
|
.then(function(iconFiles) {
|
|
|
|
debug('Found files %j', iconFiles);
|
|
|
|
var scalableIcon = iconFiles.reduce(function(scalableIcon, iconPath) {
|
|
if(iconPath.indexOf('scalable') !== -1) {
|
|
debug('Found scalable icon %s', iconPath);
|
|
scalableIcon = iconPath;
|
|
}
|
|
return scalableIcon;
|
|
}, null);
|
|
|
|
if(scalableIcon) return scalableIcon;
|
|
|
|
if(!size) {
|
|
size = Math.max.apply(Math, clean(iconFiles.map(sizeFromPath)));
|
|
}
|
|
|
|
var closestIcon = iconFiles.reduce(function(foundIcon, iconPath) {
|
|
var foundSize = sizeFromPath(iconPath);
|
|
if( foundSize && Math.abs(foundSize - size) < Math.abs(foundIcon.size - size) ) {
|
|
foundIcon.path = iconPath;
|
|
foundIcon.size = foundSize;
|
|
}
|
|
return foundIcon;
|
|
}, {path: null, size: null});
|
|
|
|
return closestIcon.path;
|
|
|
|
})
|
|
.then(function(iconPath) {
|
|
debug('Closest icon %j', iconPath);
|
|
return iconPath ? path.join(ICON_THEMES_ROOTDIR, iconPath) : null;
|
|
})
|
|
;
|
|
|
|
function sizeFromPath(iconPath) {
|
|
var simpleSizeRegex = /\/(\d+)\//;
|
|
var matches = simpleSizeRegex.exec(iconPath);
|
|
if(matches && matches[1]) return +matches[1];
|
|
var doubleSizeRegex = /\/(\d+)x\d+\//;
|
|
matches = doubleSizeRegex.exec(iconPath);
|
|
if(matches && matches[1]) return +matches[1];
|
|
}
|
|
|
|
};
|
|
|
|
exports.findPixmapsIcon = function(iconName) {
|
|
var filePattern = iconName+'.{svg,png}';
|
|
debug('Looking for pixmap icon %s', filePattern);
|
|
return System.findFiles(filePattern, {cwd: PIXMAPS_ICONS_ROOTDIR})
|
|
.then(function(iconPaths) {
|
|
iconPaths = iconPaths.map(function(iconPath) {
|
|
return path.join(PIXMAPS_ICONS_ROOTDIR, iconPath);
|
|
});
|
|
return exports._selectBestIcon(iconPaths);
|
|
})
|
|
;
|
|
};
|
|
|
|
exports.findIconThemes = function() {
|
|
return System.findFiles('*/', {cwd: ICON_THEMES_ROOTDIR, realpath: true})
|
|
.then(function(files) {
|
|
return files.map(function(f) {
|
|
return path.basename(f);
|
|
});
|
|
})
|
|
;
|
|
};
|
|
|
|
exports.loadThemeIndex = function(themeName) {
|
|
var themeIndexPath = path.join(ICON_THEMES_ROOTDIR, themeName, 'index.theme');
|
|
return System.loadINIFile(themeIndexPath);
|
|
};
|
|
|
|
exports.themeIndexExists = function(themeName) {
|
|
var themeIndexPath = path.join(ICON_THEMES_ROOTDIR, themeName, 'index.theme');
|
|
return System.exists(themeIndexPath);
|
|
};
|
|
|
|
exports._selectBestIcon = function(iconPaths) {
|
|
var iconSelection = iconPaths.reduce(function(iconSelection, iconPath) {
|
|
if(iconPath) {
|
|
var key = iconPath.indexOf('scalable') !== -1 ? 'scalable' : 'bitmap';
|
|
iconSelection[key] = iconPath;
|
|
}
|
|
return iconSelection;
|
|
}, {scalable: null, bitmap: null});
|
|
debug('Icon selection %j', iconSelection);
|
|
return iconSelection.scalable || iconSelection.bitmap;
|
|
};
|
|
|
|
|
|
// Array helpers
|
|
|
|
function clean(arr) {
|
|
return arr.filter(function(item) {
|
|
return item !== null && item !== undefined;
|
|
});
|
|
}
|
|
|
|
function flatten(arr) {
|
|
return arr.reduce(function(result, item) {
|
|
result = result.concat.apply(result, Array.isArray(item) ? flatten(item) : [item]);
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
function uniq(arr) {
|
|
return arr.reduce(function(result, item) {
|
|
if(result.indexOf(item) === -1) {
|
|
result.push(item);
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|