src/node-assets.js
// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014
/**
* Asset management functions for SAGE2 server
*
* @module server
* @submodule node-assets
* @requires color, fluent-ffmpeg, gm, json5, node-exiftool, node-utils, node-registry
*/
// require variables to be declared
"use strict";
var fs = require('fs');
var path = require('path');
var os = require('os');
var color = require('color');
var ffmpeg = require('fluent-ffmpeg'); // ffmpeg
var gm = require('gm'); // imagesmagick
var json5 = require('json5');
var mv = require('mv');
var chalk = require('chalk');
var exiftool = require('../src/node-exiftool'); // gets exif tags for images
var sageutils = require('../src/node-utils'); // provides utility functions
var registry = require('../src/node-registry');
// Global variable to handle imageMagick configuration
var imageMagick = null;
var config = null;
/**
* Class describing one asset (file or url)
*
* @class Asset
* @constructor
* @return {Object} an object representing an empty asset
*/
function Asset() {
this.filename = null;
this.url = null;
this.id = null;
this.exif = null;
}
/**
* Set an URL for an asset
*
* @method setURL
* @param aURL {String} url string
*/
Asset.prototype.setURL = function(aURL) {
this.url = aURL;
this.id = aURL;
this.sage2URL = aURL;
};
/**
* Set an filename for an asset
*
* @method setFilename
* @param aFilename {String} name of the file
*/
Asset.prototype.setFilename = function(aFilename) {
this.filename = path.resolve(aFilename);
this.id = this.filename;
// Calculate a SAGE2 URL based on the full pathname
this.sage2URL = "";
for (var f in global.mediaFolders) {
var folder = global.mediaFolders[f];
var up;
up = path.resolve(folder.path);
var pubdir = this.id.split(up);
if (pubdir.length === 2) {
this.sage2URL = sageutils.encodeReservedPath(folder.url + pubdir[1]);
}
}
};
/**
* Set the metadata for an asset
*
* @method setEXIF
* @param exifdata {Object} an object containing EXIF data
*/
Asset.prototype.setEXIF = function(exifdata) {
this.exif = exifdata;
var mime_type;
var mime_app = registry.getMimeType(this.filename);
if (mime_app) {
// if it's a type registred by an app, override the mime type
// in order to launch the right app
mime_type = mime_app;
} else {
// otherwise use the mime type from exiftool
mime_type = this.exif.MIMEType;
}
this.sage2Type = mime_type;
};
/**
* Get the width of an asset, from the EXIF data
*
* @method width
* @return {Number} width in pixel
*/
Asset.prototype.width = function() {
return this.exif.ImageWidth;
};
/**
* Get the height of an asset, from the EXIF data
*
* @method height
* @return {Number} height in pixel
*/
Asset.prototype.height = function() {
return this.exif.ImageHeight;
};
var AllAssets = null;
/**
* Asset management
*
* @class node-assets
*/
/**
* Configuration of ImageMagick and FFMpeg
*
* @method setupBinaries
* @param imOptions {Object} object containing path to binaries
* @param ffmpegOptions {Object} object containing path to binaries
*/
var setupBinaries = function(imOptions, ffmpegOptions) {
// Load the settings from the server
imageMagick = gm.subClass(imOptions);
// Set the path to binaries for video processing
if (ffmpegOptions.appPath !== undefined) {
ffmpeg.setFfmpegPath(ffmpegOptions.appPath + 'ffmpeg');
ffmpeg.setFfprobePath(ffmpegOptions.appPath + 'ffprobe');
}
};
var initializeConfiguration = function(cfg) {
config = cfg;
};
var printAssets = function() {
var idx, keys, one;
idx = 0;
// Sort by name
keys = Object.keys(AllAssets.list).sort();
// Print
for (var f in keys) {
one = AllAssets.list[keys[f]];
if (one.exif.FileSize) {
sageutils.log("Assets", idx, one.exif.FileName, one.exif.MIMEType, one.sage2URL, one.exif.FileSize);
} else {
sageutils.log("Assets", idx, one.exif.FileName, one.exif.MIMEType, one.sage2URL);
}
idx++;
}
};
var saveAssets = function(filename) {
// if parameter null, defaults
filename = filename || 'assets';
var fullpath = path.join(AllAssets.root, 'assets', filename);
// if it doesn't end in .json, add it
if (fullpath.indexOf(".json", fullpath.length - 5) === -1) {
fullpath += '.json';
}
try {
fs.writeFileSync(fullpath, JSON.stringify(AllAssets, null, 4));
} catch (err) {
sageutils.log("Assets", "error saving assets", err);
}
};
var generateImageThumbnails = function(infile, outfile, sizes, index, callback) {
// initial call, index is not specified
index = index || 0;
// are we done yet
if (index >= sizes.length) {
callback();
return;
}
imageMagick(infile + "[0]").bitdepth(8).flatten().command("convert").in("-resize", sizes[index] + "x" + sizes[index])
.in("-gravity", "center").in("-background", "rgb(71,71,71)")
.in("-extent", sizes[index] + "x" + sizes[index])
.out("-quality", "70").write(outfile + '_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] +
"x" + sizes[index] + " thumbnail for:", infile);
return;
}
// recursive call to generate the next size
generateImageThumbnails(infile, outfile, sizes, index + 1, callback);
});
};
var generatePdfThumbnailsHelper = function(intermediate, infile, outfile, sizes, index, callback) {
// initial call, index is not specified
index = index || 0;
// are we done yet
if (index >= sizes.length) {
// delete the temp fie
fs.unlinkSync(intermediate);
// we are done, trigger the callback
callback();
return;
}
imageMagick(intermediate).in("-density", "96").in("-depth", "8").in("-quality", "70")
.in("-resize", sizes[index] + "x" + sizes[index]).in("-gravity", "center")
.in("-background", "rgb(71,71,71)").in("-extent", sizes[index] + "x" + sizes[index])
.out("-quality", "70").write(outfile + '_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] + "x" + sizes[index] +
" thumbnail for:" + infile + ' -- ' + err);
return;
}
// recursive call to generate the next size
generatePdfThumbnailsHelper(intermediate, infile, outfile, sizes, index + 1, callback);
});
};
var generatePdfThumbnails = function(infile, outfile, width, height, sizes, index, callback) {
// create a temporary file (cant use the buffer API since GS spits out on stdout)
var tmpfile = path.join(os.tmpdir(), path.basename(infile) + ".jpg");
imageMagick(width, height, "#ffffff").append(infile + "[0]").colorspace("RGB").noProfile().flatten()
.write(tmpfile, function(err, buffer) {
if (err) {
sageutils.log("Assets", "cannot generate thumbnails for:" + infile + ' -- ' + err);
return;
}
generatePdfThumbnailsHelper(tmpfile, infile, outfile, sizes, index, callback);
});
};
var generateVideoThumbnails = function(infile, outfile, width, height, sizes, index, callback) {
// initial call, index is not specified
index = index || 0;
// are we done yet
if (index >= sizes.length) {
callback();
return;
}
var aspect = width / height;
var size = sizes[index] + "x" + Math.round(sizes[index] / aspect);
if (aspect < 1.0) {
size = Math.round(sizes[index] * aspect) + "x" + sizes[index];
}
ffmpeg(infile)
.on('error', function(err) {
sageutils.log("Assets", 'Error processing ' + infile);
// recursive call to generate the next size
generateVideoThumbnails(infile, outfile, width, height, sizes, index + 1, callback);
})
.on('end', function() {
var tmpImg = outfile + '_' + size + '_1.jpg';
imageMagick(tmpImg).command("convert").in("-resize", sizes[index] + "x" + sizes[index])
.in("-gravity", "center").in("-background", "rgb(71,71,71)")
.in("-extent", sizes[index] + "x" + sizes[index])
.out("-quality", "70").write(outfile + '_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] + "x" +
sizes[index] + " thumbnail for:", infile);
return;
}
fs.unlink(tmpImg, function(err2) {
if (err2) {
// throw err2;
console.log('Error', err2);
}
});
// recursive call to generate the next size
generateVideoThumbnails(infile, outfile, width, height, sizes, index + 1, callback);
});
})
.screenshots({
timestamps: ["10%"],
filename: path.basename(outfile) + "_%r_%i.jpg",
folder: path.dirname(outfile),
size: size
});
};
var generateAppThumbnails = function(infile, outfile, acolor, sizes, index, callback) {
// initial call, index is not specified
index = index || 0;
// are we done yet
if (index >= sizes.length) {
callback();
return;
}
var radius = Math.round(sizes[index] / 2);
var edge = Math.round(sizes[index] / 128);
var corner = Math.round(sizes[index] / 6.5641);
var width = Math.round(sizes[index] / 1.4382);
var circle = radius + " " + radius + " " + edge + " " + radius;
var img = corner + " " + corner + " " + width + " " + width;
imageMagick(sizes[index], sizes[index], "rgb(71,71,71)").command("convert")
.in("-fill", "rgb(" + acolor.r + "," + acolor.g + "," + acolor.b + ")")
.in("-draw", "circle " + circle).in("-draw", "image src-over " + img + " '" + infile + "'")
.out("-quality", "70").write(outfile + '_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] + "x" +
sizes[index] + " thumbnail for:", infile);
return;
}
// recursive call to generate the next size
generateAppThumbnails(infile, outfile, acolor, sizes, index + 1, callback);
});
};
var generateRemoteSiteThumbnails = function(infile, outfile, sizes, index, callback) {
// initial call, index is not specified
index = index || 0;
// are we done yet
if (index >= sizes.length) {
callback();
return;
}
var connected = "#379982";
var disconnected = "#AD2A2A";
if (config.ui.menubar &&
config.ui.menubar.remoteConnectedColor &&
config.ui.menubar.remoteConnectedColor[0] === "#") {
connected = config.ui.menubar.remoteConnectedColor;
}
if (config.ui.menubar &&
config.ui.menubar.remoteDisconnectedColor &&
config.ui.menubar.remoteDisconnectedColor[0] === "#") {
disconnected = config.ui.menubar.remoteDisconnectedColor;
}
var font = "Helvetica.ttf";
var fontSize = parseInt(0.1 * sizes[index], 10);
var finishedC = false;
var finishedD = false;
imageMagick(sizes[index], sizes[index], connected).fill("#FFFFFF").font(font, fontSize)
.drawText(0, 0, infile, "Center").write(outfile + '_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] + "x" +
sizes[index] + " thumbnail for:", infile);
return;
}
finishedC = true;
if (finishedD === true) {
// recursive call to generate the next size
generateRemoteSiteThumbnails(infile, outfile, sizes, index + 1, callback);
}
});
imageMagick(sizes[index], sizes[index], disconnected).fill("#FFFFFF").font(font, fontSize).
drawText(0, 0, infile, "Center").write(outfile + '_disconnected_' + sizes[index] + '.jpg', function(err) {
if (err) {
sageutils.log("Assets", "cannot generate " + sizes[index] + "x" +
sizes[index] + " thumbnail for:", infile);
return;
}
finishedD = true;
if (finishedC === true) {
// recursive call to generate the next size
generateRemoteSiteThumbnails(infile, outfile, sizes, index + 1, callback);
}
});
};
var addFile = function(filename, exif, callback) {
if (exif.MIMEType === 'application/vnd.adobe.photoshop') {
exif.MIMEType = 'image/vnd.adobe.photoshop';
}
// Add the asset in the array
var anAsset = new Asset();
anAsset.setFilename(filename);
anAsset.setEXIF(exif);
AllAssets.list[anAsset.id] = anAsset;
// Path for the file system
var thumb = path.join(AllAssets.root, 'assets', exif.FileName);
// Path for the https server
var rthumb = path.join(AllAssets.rel, 'assets', exif.FileName);
// If it's an image, process for thumbnail
if (exif.MIMEType.indexOf('image/') > -1) {
generateImageThumbnails(filename, thumb, [512, 256], null, function() {
callback();
});
anAsset.exif.SAGE2thumbnail = rthumb;
} else if (exif.MIMEType === 'application/pdf') {
// Dont know the width and height, so null values
generatePdfThumbnails(filename, thumb, null, null, [512, 256], null, function() {
callback();
});
anAsset.exif.SAGE2thumbnail = rthumb;
} else if (exif.MIMEType === 'text/plain') {
// Callback must be done otherwise the associated app will not launch
// Might be worth doing an else catch.
callback();
} else if (exif.MIMEType.indexOf('video/') > -1) {
generateVideoThumbnails(filename, thumb, exif.ImageWidth, exif.ImageHeight, [512, 256], null, function() {
callback();
});
anAsset.exif.SAGE2thumbnail = rthumb;
} else if (exif.MIMEType === 'application/custom' ||
exif.MIMEType === 'application/xml') {
if (exif.icon === null || !sageutils.fileExists(exif.icon)) {
anAsset.exif.SAGE2thumbnail = path.join(AllAssets.rel, 'assets', 'apps', 'unknownapp');
callback();
} else {
// Path for the node server
thumb = path.join(AllAssets.root, 'assets', 'apps', exif.FileName);
// Path for the https server
rthumb = path.join(AllAssets.rel, 'assets', 'apps', exif.FileName);
var primaryColorOfImage = function(err, buffer) {
if (err) {
throw err;
}
var result = buffer.toString();
var colors = result.substring(1, result.length - 1).split("\n");
var primaryColor = {r: 0, g: 0, b: 0};
var primaryValue = 0;
for (var i = 0; i < colors.length; i++) {
var cInfo = colors[i].trim();
var rgbStart = cInfo.indexOf("(");
var rgbEnd = cInfo.indexOf(")");
if (rgbStart < 0 || rgbEnd < 0) {
continue;
}
var rawCount = parseInt(cInfo.substring(0, rgbStart - 2), 10);
var red = parseInt(cInfo.substring(rgbStart + 1, rgbStart + 4), 10);
var green = parseInt(cInfo.substring(rgbStart + 5, rgbStart + 8), 10);
var blue = parseInt(cInfo.substring(rgbStart + 9, rgbStart + 12), 10);
var alpha = parseInt(cInfo.substring(rgbStart + 13, rgbStart + 16), 10);
var rgb = color({r: red, g: green, b: blue});
var hsv = rgb.hsv();
var ms = (hsv.s + 1) / 100;
var mv = hsv.v > 60 ? 1.0 : hsv.v > 30 ? 0.1 : 0.01;
var ma = alpha / 255;
var weighted = rawCount * ms * mv * ma;
if (weighted > primaryValue) {
primaryValue = weighted;
primaryColor.r = red;
primaryColor.g = green;
primaryColor.b = blue;
}
}
// use tinted primary color as background
var tint = 0.4; // 0.0 --> white, 1.0 --> original color
var primaryTint = {
r: Math.round(255 - ((255 - primaryColor.r) * tint)),
g: Math.round(255 - ((255 - primaryColor.g) * tint)),
b: Math.round(255 - ((255 - primaryColor.b) * tint))
};
generateAppThumbnails(exif.icon, thumb, primaryTint, [512, 256], null, function() {
callback();
});
};
imageMagick(exif.icon).command("convert").in("-colors", "32")
.in("-depth", "8").in("-format", "'%c'")
.toBuffer("histogram:info", primaryColorOfImage);
anAsset.exif.SAGE2thumbnail = rthumb;
}
} else if (exif.MIMEType === "application/remote_site") {
// Path for the node server
thumb = path.join(AllAssets.root, 'assets', 'remote', exif.FileName);
// Path for the https server
rthumb = path.join(AllAssets.rel, 'assets', 'remote', exif.FileName);
generateRemoteSiteThumbnails(filename, thumb, [512, 256, 128], null, function() {
callback();
});
anAsset.exif.SAGE2thumbnail = rthumb;
} else {
// otherwise, just call the callback
callback();
}
};
var deleteAsset = function(filename, cb) {
var elt = AllAssets.list[filename];
if (elt && elt.sage2Type === "sage2/url") {
// if it's a URL, just remove from array
delete AllAssets.list[filename];
sageutils.log("Assets", "successfully deleted URL:", filename);
// save the DB and trigger the callback
saveAssets();
if (cb) {
cb(null);
}
} else if (elt && elt.sage2Type !== "application/custom") {
// if it's a file and not an application
var filepath = path.resolve(filename);
if (filepath in AllAssets.list) {
fs.unlink(filepath, function(err) {
if (err) {
sageutils.log("Assets", "error removing file:", filename, err);
if (cb) {
cb(err);
}
} else {
sageutils.log("Assets", "successfully deleted file:", filename);
// Delete the metadata
delete AllAssets.list[filepath];
saveAssets();
if (cb) {
cb(null);
}
}
});
}
}
};
var addURL = function(aUrl, exif) {
// Add the asset in the array
var anAsset = new Asset();
anAsset.setURL(aUrl);
anAsset.setEXIF(exif);
AllAssets.list[anAsset.id] = anAsset;
saveAssets();
};
var getDimensions = function(id) {
id = path.resolve(id);
if (id in AllAssets.list) {
return {
width: AllAssets.list[id].exif.ImageWidth,
height: AllAssets.list[id].exif.ImageHeight
};
}
return null;
};
// Returns a EXIF data element
var getTag = function(id, tag) {
var result = null;
// convert the search tag to lowercase
var lowerTag = tag.toLowerCase();
// simplify the id
id = path.resolve(id);
// search for it
if (id in AllAssets.list) {
// Compare the keys in lowercase
Object.keys(AllAssets.list[id].exif).forEach(function(element, i) {
if (lowerTag === element.toLowerCase()) {
// copy the value
result = AllAssets.list[id].exif[element];
// send it back
return result;
}
});
}
return result;
};
// Add an EXIF data element
var addTag = function(id, tagName, tagValue) {
id = path.resolve(id);
if (id in AllAssets.list) {
AllAssets.list[id].exif[tagName] = tagValue;
return true;
}
return false;
};
var getURL = function(id) {
id = path.resolve(id);
if (id in AllAssets.list) {
return AllAssets.list[id].sage2URL;
}
return null;
};
var getMimeType = function(id) {
id = path.resolve(id);
if (id in AllAssets.list) {
return AllAssets.list[id].exif.MIMEType;
}
return null;
};
var getExifData = function(id) {
id = path.resolve(id);
if (id in AllAssets.list) {
return AllAssets.list[id].exif;
}
return null;
};
var exifAsync = function(cmds, cb) {
var execNext = function() {
var file = cmds.shift();
if (fs.lstatSync(file).isDirectory()) {
var instuctionsFile, instructions;
instuctionsFile = path.join(file, "instructions.json");
if (!sageutils.fileExists(instuctionsFile)) {
if (cmds.length > 0) {
return execNext();
}
return cb(null);
}
instructions = json5.parse(fs.readFileSync(instuctionsFile, 'utf8'));
var appIcon = null;
if (instructions.icon) {
appIcon = path.join(file, instructions.icon);
}
var app = path.basename(file);
sageutils.log("EXIF", "Adding", chalk.cyan.bold(app), chalk.dim("(App)"));
var metadata = {};
if (instructions.title !== undefined && instructions.title !== null && instructions.title !== "") {
metadata.title = instructions.title;
} else {
metadata.title = app;
}
if (instructions.version !== undefined && instructions.version !== null && instructions.version !== "") {
metadata.version = instructions.version;
} else {
metadata.version = "1.0.0";
}
if (instructions.description !== undefined && instructions.description !== null && instructions.description !== "") {
metadata.description = instructions.description;
} else {
metadata.description = "-";
}
if (instructions.author !== undefined && instructions.author !== null && instructions.author !== "") {
metadata.author = instructions.author;
} else {
metadata.author = "SAGE2";
}
if (instructions.license !== undefined && instructions.license !== null && instructions.license !== "") {
metadata.license = instructions.license;
} else {
metadata.license = "-";
}
if (instructions.keywords !== undefined && instructions.keywords !== null &&
Array.isArray(instructions.keywords)) {
metadata.keywords = instructions.keywords;
} else {
metadata.keywords = [];
}
if (instructions.fileTypes !== undefined && instructions.fileTypes !== null &&
Array.isArray(instructions.fileTypes) && instructions.directory !== undefined &&
instructions.directory !== null && instructions.directory !== "") {
metadata.fileTypes = instructions.fileTypes;
metadata.directory = instructions.directory;
metadata.removeFromLauncher = !!instructions.removeFromLauncher; // convert to bool
} else {
metadata.fileTypes = [];
metadata.removeFromLauncher = false;
}
var exif = {FileName: app, icon: appIcon, MIMEType: "application/custom", metadata: metadata};
addFile(file, exif, function() {
if (metadata.fileTypes.length > 0) {
var s2url = getURL(file);
registry.register(s2url, instructions.fileTypes, instructions.directory, false);
}
if (cmds.length > 0) {
execNext();
} else {
cb(null);
}
});
} else {
exiftool.file(file, function(err, data) {
if (err) {
console.log("internal error for file", file);
cb(err);
} else {
sageutils.log("EXIF", "Adding " + data.FileName);
addFile(data.SourceFile, data, function() {
if (cmds.length > 0) {
execNext();
} else {
cb(null);
}
});
}
});
}
};
if (cmds.length > 0) {
execNext();
}
};
var listAssets = function() {
var pdfs = [];
var videos = [];
var apps = [];
var images = [];
var links = [];
var others = [];
// Get all the assets
var keys = Object.keys(AllAssets.list);
for (var f in keys) {
var one = AllAssets.list[keys[f]];
if (one.exif.MIMEType === 'application/custom') {
if (!one.exif.metadata.removeFromLauncher) {
// exclude 'viewer' applications
apps.push(one);
}
} else {
var defaultApp;
// Get the app for the asset
if (!one.filename) {
defaultApp = registry.getDefaultAppFromMime(one.exif.MIMEType);
} else {
defaultApp = registry.getDefaultApp(one.filename);
}
// Put the asset in the right category
if (defaultApp === "pdf_viewer") {
pdfs.push(one);
} else if (defaultApp === "image_viewer") {
images.push(one);
} else if (defaultApp === "movie_player") {
videos.push(one);
} else if (defaultApp === "Webview") {
links.push(one);
} else {
others.push(one);
}
}
}
// Sort independently of case
images.sort(sageutils.compareFilename);
videos.sort(sageutils.compareFilename);
pdfs.sort(sageutils.compareFilename);
apps.sort(sageutils.compareFilename);
links.sort(sageutils.compareFilename);
return {
images: images, videos: videos, pdfs: pdfs,
applications: apps, links: links,
others: others
};
};
var listPDFs = function() {
var result = [];
var keys = Object.keys(AllAssets.list);
for (var f in keys) {
var one = AllAssets.list[keys[f]];
if (one.exif.MIMEType !== 'application/custom' &&
registry.getDefaultApp(one.filename) === "pdf_viewer") {
result.push(one);
}
}
return result;
};
var listImages = function() {
var result = [];
var keys = Object.keys(AllAssets.list);
for (var f in keys) {
var one = AllAssets.list[keys[f]];
if (one.exif.MIMEType !== 'application/custom' &&
registry.getDefaultApp(one.filename) === "image_viewer") {
result.push(one);
}
}
return result;
};
var listVideos = function() {
var result = [];
var keys = Object.keys(AllAssets.list);
for (var f in keys) {
var one = AllAssets.list[keys[f]];
if (one.exif.MIMEType !== 'application/custom' &&
registry.getDefaultApp(one.filename) === "movie_player") {
result.push(one);
}
}
return result;
};
var listApps = function() {
var result = [];
var keys = Object.keys(AllAssets.list);
for (var f in keys) {
var one = AllAssets.list[keys[f]];
if (one.exif.MIMEType === 'application/custom') {
result.push(one);
}
}
// Remove 'viewer' apps
var i = result.length;
while (i--) {
if (result[i].exif.metadata.removeFromLauncher) {
result.splice(i, 1);
}
}
return result;
};
var recursiveReaddirSync = function(aPath) {
var list = [];
var excludes = ['.DS_Store', 'Thumbs.db', 'tmp', 'passwd.json',
'assets', 'sessions', 'config', 'sabiConfig', 'savedFiles', 'apps'];
var excludeExtensions = ['.js', '.css'];
var files, stats;
files = fs.readdirSync(aPath);
if (files.indexOf('instructions.json') >= 0) {
// it's an application folder
list.push(aPath);
} else {
files.forEach(function(file) {
// get the file extension
var ext = path.extname(file);
// exclude bad folders and bad filenames
// and exclude bad extensions
if (excludes.indexOf(file) === -1 &&
excludeExtensions.indexOf(ext) === -1) {
stats = fs.lstatSync(path.join(aPath, file));
if (stats.isDirectory()) {
list = list.concat(recursiveReaddirSync(path.join(aPath, file)));
} else {
list.push(path.join(aPath, file));
}
}
});
}
return list;
};
var searchApplications = function(aPath) {
var list = [];
var excludes = ['assets', 'sessions', 'config', 'sabiConfig', 'web', 'savedFiles'];
var files, stats;
files = fs.readdirSync(aPath);
if (files.indexOf('instructions.json') >= 0) {
// it's an application folder
list.push(aPath);
} else {
files.forEach(function(file) {
if (excludes.indexOf(file) === -1) {
stats = fs.lstatSync(path.join(aPath, file));
if (stats.isDirectory()) {
list = list.concat(searchApplications(path.join(aPath, file)));
}
}
});
}
return list;
};
var refreshApps = function(root, callback) {
var allApps = searchApplications(root);
var thelist = [];
var i;
var item;
for (i = 0; i < allApps.length; i++) {
item = path.resolve(allApps[i]);
thelist.push(item);
}
if (thelist.length > 0) {
sageutils.log("EXIF", "Starting processing:", thelist.length, "items");
exifAsync(thelist, function(err) {
if (err) {
sageutils.log("EXIF", chalk.red.bold("Error:", err));
} else {
sageutils.log("EXIF", chalk.green.bold("Processing finished for " + root));
if (callback) {
callback(thelist.length);
}
}
});
} else {
if (callback) {
callback(0);
}
}
};
var refreshAssets = function(root, callback) {
var thelist = [];
var i;
var item;
// Populate a list for this folder
var uploaded = recursiveReaddirSync(root);
for (i = 0; i < uploaded.length; i++) {
item = path.resolve(uploaded[i]);
if (item in AllAssets.list) {
AllAssets.list[item].valid = true;
} else {
thelist.push(item);
}
}
if (thelist.length > 0) {
sageutils.log("EXIF", "Starting processing:", thelist.length, "items");
exifAsync(thelist, function(err) {
if (err) {
sageutils.log("EXIF", "Error:", err);
} else {
sageutils.log("EXIF", "Processing finished for " + root);
if (callback) {
callback(thelist.length);
}
}
});
} else {
if (callback) {
callback(0);
}
}
};
var initialize = function(mainFolder, mediaFolders, whenDone) {
if (AllAssets === null) {
var i;
var thelist = [];
var root = mainFolder.path;
var relativePath = mainFolder.url;
sageutils.log("Assets", 'Main asset folder:', chalk.yellow.bold(root));
// Make sure the asset folder exists
var assetFolder = path.join(root, 'assets');
if (!sageutils.folderExists(assetFolder)) {
fs.mkdirSync(assetFolder);
}
registry.initialize(assetFolder);
// Make sure the asset/apps folder exists
var assetAppsFolder = path.join(assetFolder, 'apps');
if (!sageutils.folderExists(assetAppsFolder)) {
fs.mkdirSync(assetAppsFolder);
}
// Make sure unknownapp images exist
var unknownapp_256Img = path.resolve('public', 'images', 'unknownapp_256.jpg');
var unknownapp_256 = path.join(assetAppsFolder, 'unknownapp_256.jpg');
if (!sageutils.fileExists(unknownapp_256)) {
fs.createReadStream(unknownapp_256Img).pipe(fs.createWriteStream(unknownapp_256));
}
var unknownapp_512Img = path.resolve('public', 'images', 'unknownapp_512.jpg');
var unknownapp_512 = path.join(assetAppsFolder, 'unknownapp_512.jpg');
if (!sageutils.fileExists(unknownapp_512)) {
fs.createReadStream(unknownapp_512Img).pipe(fs.createWriteStream(unknownapp_512));
}
// Make sure the asset/remote folder exists
var assetRemoteFolder = path.join(assetFolder, 'remote');
if (!sageutils.folderExists(assetRemoteFolder)) {
fs.mkdirSync(assetRemoteFolder);
}
AllAssets = {};
AllAssets.mainFolder = mainFolder;
var assetFile = path.join(assetFolder, 'assets.json');
if (sageutils.fileExists(assetFile)) {
var data = fs.readFileSync(assetFile);
var oldList = JSON.parse(data);
AllAssets.root = root;
AllAssets.rel = relativePath;
AllAssets.list = oldList.list;
// Flag all the assets for checking
for (var it in AllAssets.list) {
AllAssets.list[it].valid = false;
}
} else {
AllAssets.list = {};
AllAssets.root = root;
AllAssets.rel = relativePath;
}
refreshApps(root, function() {
refreshAssets(root, function() {
// Finally, delete the elements which are not there anymore
for (var item in AllAssets.list) {
if (item.startsWith(root) && AllAssets.list[item].valid === false) {
sageutils.log("Assets", "Removing old item", item);
delete AllAssets.list[item];
} else {
// Just remove the valid flag
delete AllAssets.list[item].valid;
}
}
saveAssets();
// callback when done
if (whenDone) {
whenDone();
}
});
});
// Extra folders
AllAssets.mediaFolders = mediaFolders;
for (var mf in mediaFolders) {
var f = mediaFolders[mf];
if (root !== f.path) {
// Adding all the other folders (except the main one)
addAssetFolder(f.path, whenDone);
}
}
// Remote sites in assets ?
for (i = 0; i < config.remote_sites.length; i++) {
if (config.remote_sites[i].name in AllAssets.list) {
AllAssets.list[config.remote_sites[i].name].valid = true;
} else {
thelist.push(config.remote_sites[i].name);
}
}
}
};
var addAssetFolder = function(root, whenDone) {
sageutils.log("Assets", 'Adding asset folder:', chalk.yellow.bold(root));
// Make sure the asset folder exists
var assetFolder = path.join(root, 'assets');
if (!sageutils.folderExists(assetFolder)) {
fs.mkdirSync(assetFolder);
}
// Make sure the asset/apps folder exists
var assetAppsFolder = path.join(assetFolder, 'apps');
if (!sageutils.folderExists(assetAppsFolder)) {
fs.mkdirSync(assetAppsFolder);
}
// Make sure unknownapp images exist
var unknownapp_256Img = path.resolve('public', 'images', 'unknownapp_256.jpg');
var unknownapp_256 = path.join(assetAppsFolder, 'unknownapp_256.jpg');
if (!sageutils.fileExists(unknownapp_256)) {
fs.createReadStream(unknownapp_256Img).pipe(fs.createWriteStream(unknownapp_256));
}
var unknownapp_512Img = path.resolve('public', 'images', 'unknownapp_512.jpg');
var unknownapp_512 = path.join(assetAppsFolder, 'unknownapp_512.jpg');
if (!sageutils.fileExists(unknownapp_512)) {
fs.createReadStream(unknownapp_512Img).pipe(fs.createWriteStream(unknownapp_512));
}
refreshApps(root, function() {
refreshAssets(root, function() {
// Finally, delete the elements which are not there anymore
for (var item in AllAssets.list) {
if (item.startsWith(root) && AllAssets.list[item].valid === false) {
sageutils.log("Assets", "Removing old item", item);
delete AllAssets.list[item];
} else {
// Just remove the valid flag
delete AllAssets.list[item].valid;
}
}
saveAssets();
// callback when done
if (whenDone) {
whenDone();
}
});
});
};
// regenrate all the assets thumbnails and EXIF data
// (needed with version upgrades)
var regenerateAssets = function() {
// Make sure the asset folder exists
var assetFolder = path.join(AllAssets.root, 'assets');
if (!sageutils.folderExists(assetFolder)) {
fs.mkdirSync(assetFolder);
}
var assetFile = path.join(assetFolder, 'assets.json');
if (sageutils.fileExists(assetFile)) {
fs.unlinkSync(assetFile);
sageutils.log("Assets", "successfully deleted", assetFile);
}
// var rootdir = AllAssets.root;
// var relativ = AllAssets.rel;
var mediaf = AllAssets.mediaFolders;
var mainf = AllAssets.mainFolder;
AllAssets = null;
initialize(mainf, mediaf);
};
// Move an asset
// and process the new location
var moveAsset = function(source, destination, callback) {
// Move the file
mv(source, destination, function(err) {
if (err) {
callback(err);
} else {
// Reprocess the new asset
exifAsync([destination], function() {
// if all good, delete the source
delete AllAssets.list[source];
saveAssets();
callback(null);
});
}
});
};
exports.initialize = initialize;
exports.refresh = refreshAssets;
exports.addAssetFolder = addAssetFolder;
exports.printAssets = printAssets;
exports.saveAssets = saveAssets;
exports.listImages = listImages;
exports.listPDFs = listPDFs;
exports.listVideos = listVideos;
exports.listApps = listApps;
exports.listAssets = listAssets;
exports.addFile = addFile;
exports.addURL = addURL;
exports.regenerateAssets = regenerateAssets;
exports.exifAsync = exifAsync;
exports.deleteAsset = deleteAsset;
exports.moveAsset = moveAsset;
exports.getDimensions = getDimensions;
exports.getMimeType = getMimeType;
exports.getExifData = getExifData;
exports.getTag = getTag;
exports.addTag = addTag;
exports.getURL = getURL;
exports.initializeConfiguration = initializeConfiguration;
exports.setupBinaries = setupBinaries;