diff options
Diffstat (limited to 'build/webapp-zip.js')
-rw-r--r-- | build/webapp-zip.js | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/build/webapp-zip.js b/build/webapp-zip.js new file mode 100644 index 0000000..1c6df44 --- /dev/null +++ b/build/webapp-zip.js @@ -0,0 +1,293 @@ + +function debug(msg) { + //dump('-*- webapps-zip.js ' + msg + '\n'); +} + +// Header values usefull for zip xpcom component +const PR_RDONLY = 0x01; +const PR_WRONLY = 0x02; +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_APPEND = 0x10; +const PR_TRUNCATE = 0x20; +const PR_SYNC = 0x40; +const PR_EXCL = 0x80; + +/** + * Add a file or a directory, recursively, to a zip file + * + * @param {nsIZipWriter} zip zip xpcom instance. + * @param {String} pathInZip relative path to use in zip. + * @param {nsIFile} file file xpcom to add. + */ +function addToZip(zip, pathInZip, file) { + if (isSubjectToBranding(file.path)) { + file.append((OFFICIAL == 1) ? 'official' : 'unofficial'); + } + + if (!file.exists()) + throw new Error('Can\'t add inexistent file to zip : ' + file.path); + + // nsIZipWriter should not receive any path starting with `/`, + // it would put files in a folder with empty name... + pathInZip = pathInZip.replace(/^\/+/, ''); + + // Case 1/ Regular file + if (file.isFile()) { + try { + debug(' +file to zip ' + pathInZip); + + if (/\.html$/.test(file.leafName)) { + // this file might have been pre-translated for the default locale + let l10nFile = file.parent.clone(); + l10nFile.append(file.leafName + '.' + GAIA_DEFAULT_LOCALE); + if (l10nFile.exists()) { + zip.addEntryFile(pathInZip, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + l10nFile, + false); + return; + } + } + + let re = new RegExp('\\.html\\.' + GAIA_DEFAULT_LOCALE); + if (!zip.hasEntry(pathInZip) && !re.test(file.leafName)) { + zip.addEntryFile(pathInZip, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + file, + false); + } + } catch (e) { + throw new Error('Unable to add following file in zip: ' + + file.path + '\n' + e); + } + } + // Case 2/ Directory + else if (file.isDirectory()) { + debug(' +directory to zip ' + pathInZip); + + if (!zip.hasEntry(pathInZip)) + zip.addEntryDirectory(pathInZip, file.lastModifiedTime, false); + + // Append a `/` at end of relative path if it isn't already here + if (pathInZip.substr(-1) !== '/') + pathInZip += '/'; + + let files = ls(file); + files.forEach(function(subFile) { + let subPath = pathInZip + subFile.leafName; + addToZip(zip, subPath, subFile); + }); + } +} + +/** + * Copy a "Building Block" (i.e. shared style resource) + * + * @param {nsIZipWriter} zip zip xpcom instance. + * @param {String} blockName name of the building block to copy. + * @param {String} dirName name of the shared directory to use. + */ +function copyBuildingBlock(zip, blockName, dirName) { + let dirPath = '/shared/' + dirName + '/'; + + // Compute the nsIFile for this shared style + let styleFolder = Gaia.sharedFolder.clone(); + styleFolder.append(dirName); + let cssFile = styleFolder.clone(); + if (!styleFolder.exists()) { + throw new Error('Using inexistent shared style: ' + blockName); + } + + cssFile.append(blockName + '.css'); + addToZip(zip, dirPath + blockName + '.css', cssFile); + + // Copy everything but index.html and any other HTML page into the + // style/<block> folder. + let subFolder = styleFolder.clone(); + subFolder.append(blockName); + ls(subFolder, true).forEach(function(file) { + let relativePath = file.getRelativeDescriptor(styleFolder); + // Ignore HTML files at style root folder + if (relativePath.match(/^[^\/]+\.html$/)) + return; + // Do not process directory as `addToZip` will add files recursively + if (file.isDirectory()) + return; + addToZip(zip, dirPath + relativePath, file); + }); +} + +let webappsTargetDir = Cc['@mozilla.org/file/local;1'] + .createInstance(Ci.nsILocalFile); +webappsTargetDir.initWithPath(PROFILE_DIR); + +// Create profile folder if doesn't exists +ensureFolderExists(webappsTargetDir); + +// Create webapps folder if doesn't exists +webappsTargetDir.append('webapps'); +ensureFolderExists(webappsTargetDir); + +Gaia.webapps.forEach(function(webapp) { + // If BUILD_APP_NAME isn't `*`, we only accept one webapp + if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME) + return; + + // Compute webapp folder name in profile + let webappTargetDir = webappsTargetDir.clone(); + webappTargetDir.append(webapp.domain); + ensureFolderExists(webappTargetDir); + + let zip = Cc['@mozilla.org/zipwriter;1'].createInstance(Ci.nsIZipWriter); + + let mode = PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE; + + let zipFile = webappTargetDir.clone(); + zipFile.append('application.zip'); + zip.open(zipFile, mode); + + // Add webapp folder to the zip + debug('# Create zip for: ' + webapp.domain); + let files = ls(webapp.sourceDirectoryFile); + files.forEach(function(file) { + // Ignore l10n files if they have been inlined + if (GAIA_INLINE_LOCALES && + (file.leafName === 'locales' || file.leafName === 'locales.ini')) + return; + // Ignore files from /shared directory (these files were created by + // Makefile code). Also ignore files in the /test directory. + if (file.leafName !== 'shared' && file.leafName !== 'test') + addToZip(zip, '/' + file.leafName, file); + }); + + // Put shared files, but copy only files actually used by the webapp. + // We search for shared file usage by parsing webapp source code. + let EXTENSIONS_WHITELIST = ['html']; + let SHARED_USAGE = + /<(?:script|link).+=['"]\.?\.?\/?shared\/([^\/]+)\/([^''\s]+)("|')/g; + + let used = { + js: [], // List of JS file paths to copy + locales: [], // List of locale names to copy + resources: [], // List of resources to copy + styles: [], // List of stable style names to copy + unstable_styles: [] // List of unstable style names to copy + }; + + let files = ls(webapp.sourceDirectoryFile, true); + files.filter(function(file) { + // Process only files that may require a shared file + let extension = file.leafName + .substr(file.leafName.lastIndexOf('.') + 1) + .toLowerCase(); + return file.isFile() && EXTENSIONS_WHITELIST.indexOf(extension) != -1; + }). + forEach(function(file) { + // Grep files to find shared/* usages + let content = getFileContent(file); + while ((matches = SHARED_USAGE.exec(content)) !== null) { + let kind = matches[1]; // js | locales | resources | style + let path = matches[2]; + switch (kind) { + case 'js': + if (used.js.indexOf(path) == -1) + used.js.push(path); + break; + case 'locales': + if (!GAIA_INLINE_LOCALES) { + let localeName = path.substr(0, path.lastIndexOf('.')); + if (used.locales.indexOf(localeName) == -1) { + used.locales.push(localeName); + } + } + break; + case 'resources': + if (used.resources.indexOf(path) == -1) { + used.resources.push(path); + } + break; + case 'style': + let styleName = path.substr(0, path.lastIndexOf('.')); + if (used.styles.indexOf(styleName) == -1) + used.styles.push(styleName); + break; + case 'style_unstable': + let unstableStyleName = path.substr(0, path.lastIndexOf('.')); + if (used.unstable_styles.indexOf(unstableStyleName) == -1) + used.unstable_styles.push(unstableStyleName); + break; + } + } + }); + + used.js.forEach(function(path) { + // Compute the nsIFile for this shared JS file + let file = Gaia.sharedFolder.clone(); + file.append('js'); + path.split('/').forEach(function(segment) { + file.append(segment); + }); + if (!file.exists()) { + throw new Error('Using inexistent shared JS file: ' + path + ' from: ' + + webapp.domain); + } + addToZip(zip, '/shared/js/' + path, file); + }); + + used.locales.forEach(function(name) { + // Compute the nsIFile for this shared locale + let localeFolder = Gaia.sharedFolder.clone(); + localeFolder.append('locales'); + let ini = localeFolder.clone(); + localeFolder.append(name); + if (!localeFolder.exists()) { + throw new Error('Using inexistent shared locale: ' + name + ' from: ' + + webapp.domain); + } + ini.append(name + '.ini'); + if (!ini.exists()) + throw new Error(name + ' locale doesn`t have `.ini` file.'); + + // Add the .ini file + addToZip(zip, '/shared/locales/' + name + '.ini', ini); + // And the locale folder itself + addToZip(zip, '/shared/locales/' + name, localeFolder); + }); + + used.resources.forEach(function(path) { + // Compute the nsIFile for this shared resource file + let file = Gaia.sharedFolder.clone(); + file.append('resources'); + path.split('/').forEach(function(segment) { + file.append(segment); + if (isSubjectToBranding(file.path)) { + file.append((OFFICIAL == 1) ? 'official' : 'unofficial'); + } + }); + if (!file.exists()) { + throw new Error('Using inexistent shared resource: ' + path + + ' from: ' + webapp.domain + '\n'); + return; + } + addToZip(zip, '/shared/resources/' + path, file); + }); + + used.styles.forEach(function(name) { + try { + copyBuildingBlock(zip, name, 'style'); + } catch (e) { + throw new Error(e + ' from: ' + webapp.domain); + } + }); + + used.unstable_styles.forEach(function(name) { + try { + copyBuildingBlock(zip, name, 'style_unstable'); + } catch (e) { + throw new Error(e + ' from: ' + webapp.domain); + } + }); + + zip.close(); +}); |