var path = require('path'); var System = require('./system'); var debug = require('./debug')('desktop-apps'); var Cache = require('./cache'); var promises = require('./promises'); // 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) { return promises.seq(filePaths, function(path) { return exports.loadDesktopFile(path); }) .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]; } return promises.seq(baseDirs, function(baseDir) { return System.findFiles('**/*.desktop', {cwd: baseDir, realpath: true}); }) .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 || []; return promises.seq(themes, function(theme) { return exports.findIcon(iconName, theme, size, themeIgnore); }) .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); return promises.seq(parents, function(themeName) { return exports.findIcon(iconName, themeName, size, themeIgnore); }) .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; }, []); }