ImageGrid/ui/compatibility.js
Alex A. Naanou c35d025a2f fix to last fix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2014-08-09 01:42:03 +04:00

531 lines
14 KiB
JavaScript
Executable File

/**********************************************************************
*
*
*
**********************************************************************/
//var DEBUG = DEBUG != null ? DEBUG : true
// XXX move this to the config...
var PREVIEW_SIZES = [
// NOTE: this is first so as to prevent the hi-res from loading...
// XXX this is best to be screen sized or just a little bigger...
1280,
150,
350,
900
]
/*********************************************************************/
// load the target-specific handlers...
// CEF
if(window.CEF_dumpJSON != null){
console.log('CEF mode: loading...')
var dumpJSON = CEF_dumpJSON
var listDir = CEF_listDir
var removeFile = CEF_removeFile
var runSystem = CEF_runSystem
// node-webkit
} else if(window.require != null){
console.log('node-webkit mode: loading...')
var path = require('path')
var fs = require('fs')
var fse = require('fs.extra')
var proc = require('child_process')
var node_crypto = require('crypto')
//var exif = require('exif2')
var gui = require('nw.gui')
window.osPath = function(p){
return path
// we can have two types of path:
// file:///some/path -> /some/path
// file:///X:/some/other/path -> X:/some/other/path
.normalize(p.replace(/file:\/+([a-zA-Z]:\/|\/)/, '$1'))
}
window.execPathPush = function(p){
process.env.PATH += ';' + path.normalize(path.dirname(process.execPath) + '/' + p)
}
// paths to included utils...
execPathPush('./vips/bin')
// Things ImageGrid needs...
// XXX do we need assync versions??
window.listDir = function(path){
if(!fs.existsSync(osPath(path))){
return null
}
return fs.readdirSync(osPath(path))
}
// XXX make this work across fs...
// XXX this will not overwrite...
// XXX set ctime to the same value as the original...
window.copyFile = function(src, dst){
var deferred = $.Deferred()
src = osPath(src)
dst = osPath(dst)
var path = dst.split(/[\\\/]/)
path.pop()
path = path.join('/')
// make dirs...
if(!fs.existsSync(path)){
console.log('making:', path)
fse.mkdirRecursiveSync(path)
}
if(!fs.existsSync(dst)){
// NOTE: this is not sync...
fse.copy(src, dst, function(err){
if(err){
deferred.reject(err)
} else {
deferred.resolve()
}
})
return deferred
}
deferred.notify(dst, 'exists')
return deferred.resolve()
}
window.dumpJSON = function(path, data){
path = osPath(path)
var dirs = path.split(/[\\\/]/)
dirs.pop()
dirs = dirs.join('/')
// build path...
if(!fs.existsSync(dirs)){
console.log('making:', path)
fse.mkdirRecursiveSync(path)
}
return fs.writeFileSync(path, JSON.stringify(data), encoding='utf8')
}
window.removeFile = function(path){
return fs.unlinkSync(osPath(path))
}
window.runSystem = function(path){
return proc.exec('"'+osPath(path)+'"', function(error, stdout, stderr){
if(error != null){
console.error(stderr)
}
})
}
// XXX this uses vips...
window.getVipsField = function(field, source){
if(source in IMAGES){
var img = IMAGES[source]
var source = normalizePath(img.path)
}
var getter = $.Deferred()
var data = ''
var p = proc.spawn('vips', ['im_header_string', field, osPath(source)])
p.stdout.on('data', function(d){
data += d.toString()
})
p.stdout.on('end', function(){
getter.resolve(data.trim())
})
/* XXX do we need these???
p.on('error', function(code){
// XXX
})
p.on('close', function(code){
getter.resolve(data)
})
*/
return getter
}
// NOTE: source can be either gid or a path...
window.getImageOrientation = function(source){
var getter = $.Deferred()
getVipsField('exif-ifd0-Orientation', source)
.done(function(o){
getter.resolve(orientationExif2ImageGrid(parseInt(o)))
})
return getter
}
// NOTE: source can be either gid or a path...
// XXX handle errors...
window._getImageSize = function(dimension, source){
if(source in IMAGES){
var img = IMAGES[source]
var source = normalizePath(img.path)
}
var getter = $.Deferred()
// get max/min dimension...
if(dimension == 'max' || dimension == 'min'){
$.when(
_getImageSize('width', source),
_getImageSize('height', source))
.done(function(w, h){
getter.resolve(Math[dimension](w, h))
})
// get dimension...
} else if(dimension == 'width' || dimension == 'height') {
getVipsField(dimension, source)
.done(function(res){
getter.resolve(parseInt(res))
})
// wrong dimension...
} else {
return getter.reject('unknown dimension:' + dimension)
}
return getter
}
// XXX API to add to $PATH...
// preview generation...
//
// possible modes:
// - optimized
// use closest rscale and minimal factor
// previews might get artifacts associated with small scale factors
// 0.5x time
// - best
// only use scale factor (rscale=1)
// 1x time (fixed set of previews: 1280, 150, 350, 900)
// - fast_r
// make previews using nearest rscale (factor is rounded)
// will produce inexact preview sizes
// 0.4x time
// - fast_f
// same as fast_r but factor is floored rather than rounded
// will priduce previews the same size or larger than requested
// - rscale
// only use rscale (factor=1)
// produces only fixed size previews
// 0.3x time
//
// NOTE: rscale should be used for exactly tuned preview sizes...
// NOTE: this will add already existing previews to IMAGES[gid]...
//
// XXX make this not just vips-specific...
// XXX path handling is a mess...
// XXX looks a bit too complex for what it is -- revise!
window.makeImagePreviews = function(gid, sizes, mode, no_update_loaded){
mode = mode == null ? 'fast_f' : mode
var cache_dir = CONFIG.cache_dir
var img = IMAGES[gid]
var source = normalizePath(img.path)
var name = gid +' - '+ source.split(/[\\\/]/).pop()
var compression = 90
var previews = []
// prepare the sizes we are going to be working with...
if(sizes == null){
sizes = PREVIEW_SIZES
} else if(typeof(sizes) == typeof(123)){
sizes = [ sizes ]
}
// build usable local path (without 'file:///')...
var cache_path = normalizePath(cache_dir)
cache_path = osPath(cache_path)
// get cur image size...
var size_getter = _getImageSize('max', source)
for(var i=0; i < sizes.length; i++){
var size = sizes[i]
// XXX get this from config...
var target_path = [ cache_path, size+'px' ].join('/')
var deferred = $.Deferred()
previews.push(deferred)
[function(size, target_path, deferred){
// wait for current image size if needed...
size_getter.done(function(source_size){
// handle existing previews...
if(fs.existsSync(target_path +'/'+ name)){
// see if we know about the preview...
if(img.preview == null || !((size+'px') in img.preview)){
var preview_path = [target_path, name].join('/')
// add the preview to the image object...
img.preview[size+'px'] = './' + cache_dir +'/'+ preview_path.split(cache_dir).pop()
// mark image dirty...
imageUpdated(gid)
}
//console.log('>>> Preview:', name, '('+size+'): Exists.')
deferred.notify(gid, size, 'exists')
return deferred.resolve()
// skip previews larger than cur image...
} else if(source_size <= size){
//console.log('>>> Preview:', name, '('+size+'): Skipped.')
deferred.notify(gid, size, 'skipped')
return deferred.resolve()
}
// create the directory then go to its content...
// XXX check for errors...
fse.mkdirRecursive(target_path, function(err){
var preview_path = [target_path, name].join('/')
var factor = source_size / size
// this can be 1, 2, 4 or 8...
var rscale = 1
// speed things up with read-scaling and rounding the scale factor...
if(['fast_r', 'fast_f', 'optimized', 'rscale'].indexOf(mode) >= 0){
while(rscale < 8){
if(rscale*2 >= factor){
break
}
rscale *= 2
}
factor = factor / rscale
}
// factor processing...
if(mode == 'fast_r'){
factor = Math.max(Math.round(factor), 1)
} else if(mode == 'fast_f'){
// NOTE: .floor(...) will make the images larger than
// the requested size, this will avaoid scale-up
// artifacts...
factor = Math.max(Math.floor(factor), 1)
} else if(mode == 'rscale'){
factor = 1
}
var p = proc.spawn('vips', [
'im_shrink',
osPath(source) +':'+ rscale,
preview_path +':'+ compression,
factor,
factor
])
// XXX is this the correct wat to deal with errors???
var error = ''
p.stderr.on('data', function(data){
error += data.toString()
})
//p.stderr.on('end', function(data){
//})
p.on('close', function(code){
// error...
if(code != 0){
deferred.notify(gid, size, 'error', error)
deferred.reject()
// ok...
} else {
// NOTE: the size of the real preview
// generated might different from
// the target size...
deferred.notify(gid, size, 'done')
// update the image structure...
if(!('preview' in img)){
img.preview = {}
}
img.preview[size+'px'] = './' + cache_dir +'/'+ preview_path.split(cache_dir).pop()
// mark image dirty...
imageUpdated(gid)
// we are done...
deferred.resolve()
}
})
})
})
}(size, target_path, deferred)]
}
var res = $.when.apply(null, previews)
// update loaded images...
if(!no_update_loaded){
res.done(function(){
var o = getImage(gid)
if(o.length > 0){
updateImage(o)
}
})
}
return res
}
// XXX needs more testing...
// - for some reason this is a bit slower than the queued version
// ...in spite of being managed by node.js
// - will this be faster on SMP/multi-core?
window.makeImagesPreviews = function(gids, sizes, mode){
gids = gids == null ? getClosestGIDs() : gids
return $.when.apply(null, gids.map(function(gid){
return makeImagePreviews(gid, sizes, mode)
}))
}
// Queued version of makeImagesPreviews(...)
window.makeImagesPreviewsQ = function(gids, sizes, mode){
gids = gids == null ? getClosestGIDs() : gids
var queue = getWorkerQueue('Generate previews', 4)
.filling()
// attach the workers to the queue...
$.each(gids, function(_, gid){
queue.enqueue(makeImagePreviews, gid, sizes, mode)
// XXX do we need to report seporate previews???
//.progress(function(state){ queue.notify(state) })
.always(function(){ console.log(gid, 'done') })
})
return queue.doneFilling()
}
// format: "20130102-122315"
window.getEXIFDate = function(source){
var getter = $.Deferred()
getVipsField('exif-ifd0-Date and Time', source)
.done(function(date){
getter.resolve(date
// remove substrings in braces...
.replace(/\([^)]*\)/, '')
.trim()
.replace(/:/g, '')
.replace(/ /g, '-'))
})
return getter
}
window.getEXIFGID = function(source, make_text_gid){
if(source in IMAGES){
var img = IMAGES[source]
var source = normalizePath(img.path)
}
var getter = $.Deferred()
$.when(
getVipsField('exif-ifd0-Artist', source),
getEXIFDate(source))
.done(function(artist, date){
// Artist...
artist = artist
// remove substrings in braces...
.replace(/\([^)]*\)/, '')
.trim()
artist = artist == '' ? 'Unknown' : artist
// Date...
// XXX if not set, get ctime...
// XXX
// File name...
var name = source.split(/[\\\/]/).pop().split('.')[0]
var text_gid = artist +'-'+ date +'-'+ name
// text gid...
if(make_text_gid){
getter.resolve(text_gid)
// hex gid...
} else {
var h = node_crypto.createHash('sha1')
h.update(text_gid)
var hex_gid = h.digest('hex')
getter.resolve(hex_gid)
}
})
// XXX handle arrors in a more informative way...
.fail(function(){
getter.reject()
})
return getter
}
// UI-specific...
window.toggleFullscreenMode = createCSSClassToggler(
document.body,
'.full-screen-mode',
function(action){
gui.Window.get().toggleFullscreen()
})
window.closeWindow = function(){
gui.Window.get().close()
}
window.showDevTools = function(){
gui.Window.get().showDevTools()
}
window.reload = function(){
gui.Window.get().reload()
}
window.setWindowTitle = function(text){
var title = text +' - '+ CONFIG.app_name
gui.Window.get().title = title
$('.title-bar .title').text(title)
}
// load UI stuff...
$(function(){
$('<div class="title-bar"/>')
.append($('<div class="title"></div>')
.text($('title').text()))
.append($('<div class="button close" onclick="closeWindow()">&times;</div>'))
.appendTo($('body'))
})
// PhoneGap
} else if(false){
console.log('PhoneGap mode: loading...')
// XXX
// stubs...
window.toggleFullscreenMode = function(){}
window.closeWindow = function(){}
window.showDevTools = function(){}
window.reload = function(){}
// Bare Chrome...
} else {
console.log('Chrome mode: loading...')
// stubs...
window.toggleFullscreenMode = function(){}
window.closeWindow = function(){}
window.showDevTools = function(){}
window.reload = function(){}
}
/**********************************************************************
* vim:set ts=4 sw=4 : */