ImageGrid/ui/files.js

1243 lines
31 KiB
JavaScript
Raw Normal View History

/**********************************************************************
*
*
*
**********************************************************************/
//var DEBUG = DEBUG != null ? DEBUG : true
// Flag indicating a new image file was constructed...
var IMAGES_CREATED = false
var IMAGE_PATTERN = /.*\.(jpg|jpeg|png|gif)$/i
var FILE_LOADERS = []
var FILE_SAVERS = {}
var FILES_UPDATED = []
/**********************************************************************
* File storage (Extension API -- CEF/PhoneGap/...)
*
* XXX need to cleanup this section...
*/
/********************************************************* Helpers ***/
function makeBaseFilename(base, ext){
ext = ext == null ? CONFIG.json_ext : ext
return CONFIG.base_file_pattern
.replace('${BASE}', base)
.replace('${EXT}', ext)
}
function makeFilename(base, date, ext){
date = date == null ? Date.timeStamp() : date
ext = ext == null ? CONFIG.json_ext : ext
return CONFIG.file_pattern
.replace('${DATE}', date)
.replace('${BASE}', base)
.replace('${EXT}', ext)
}
function makeFilenamePattern(base, ext){
return RegExp(makeFilename(base, '^[0-9]*', ext)+'$')
}
function makeDiffFilename(base, date, diff, ext){
date = date == null ? Date.timeStamp() : date
diff = diff == null ? CONFIG.diff_suffix : diff
ext = ext == null ? CONFIG.json_ext : ext
return CONFIG.diff_file_pattern
.replace('${DATE}', date)
.replace('${BASE}', base)
.replace('${DIFF_SUFIX}', diff)
.replace('${EXT}', ext)
}
function makeDiffFilePattern(base, diff, ext){
return RegExp(makeDiffFilename(base, '^[0-9]*', ext)+'$')
}
// Report deferred progress
//
// This uses showStatus(...) and showErrorStatus(...) to report actions.
//
// Will use showErrorStatus(...) iff "Error" is the last argument of the
// progress/notify action, removing it (string 'Error') from the arguments.
//
// Will return the original deferred.
function statusNotify(prefix, loader, not_queued){
not_queued = not_queued == null ? true : not_queued
var report = not_queued == true ? showStatus : showStatusQ
if(loader == null){
loader = prefix
prefix = null
}
return loader
.progress(function(){
var args = $.makeArray(arguments)
var getter = args[args.length-1]
if(getter != null && getter.isResolved != null){
args.pop()
.done(function(){
report(args.join(': '))
})
.fail(function(){
showErrorStatus(args.join(': '))
})
return
}
if(prefix != null && prefix != ''){
args.splice(0, 0, prefix)
}
if(args.indexOf('Error') >= 0){
args.pop()
return showErrorStatus(args.join(': '))
}
return report(args.join(': '))
})
}
// Report progress status via a progress bar...
//
// <msg> (5 of 500) x
// |============>----------------------|
//
//
// The tracker is optional, if not given a generic deferred object will
// be created and used.
//
// If close_button is false, no close button will be dosplayed, hidden
// by default.
//
// Returns the tracker object.
//
// The progress is read of the tracker (Deferred) and reported based
// on progress notifications on it.
//
// Two types of notification are supported:
// - deferred
// the .progress(..) handler will receive a deferred as a last
// argument, this will in turn do two things:
// 1) increment the max value of the progress bar in place
// 2) when the defered is done, increment the value of the
// progress bar
// - simple
// increment both max and value of the progress bar
//
// The progress bar will go into a "done" state and close if the
// tracker deferred is explicitly resolved.
//
//
// See: progressBar(..) for more details on the actual widget.
//
// NOTE: closing the progress bar will not do anything...
function statusProgress(msg, tracker, close_button){
tracker = tracker == null ? $.Deferred() : null
close_button = close_button == null ? false : close_button
var progress = progressBar(msg, null, close_button)
var total = 0
var done = 0
// XXX do we need a fail handler here???
return tracker
.done(function(){
// XXX for some reason this does not allways display "done"
closeProgressBar(progress)
})
.fail(function(){
closeProgressBar(progress, 'error')
})
.progress(function(){
var args = $.makeArray(arguments)
var getter = args[args.length-1]
total += 1
// the getter is a deferred...
if(getter != null && getter.isResolved != null){
args.pop()
.always(function(){
done += 1
updateProgressBar(progress, done, total)
})
// no getter...
} else {
done += 1
}
updateProgressBar(progress, done, total)
})
}
// Bubble up actions in the deferred chain
//
// i.e. proxy them form one deferred (from) to the next (to)...
//
// Will chain progress/notify and if only_progress is not set, also
// done/resolve and fail/reject from "from" to "to" deferred objects.
//
// Will add prefix to the list of arguments of progress/notify and
// fail/reject (if not disabled), unless it is set to null.
//
// Will return "from" object.
function bubbleProgress(prefix, from, to, only_progress){
only_progress = only_progress == null ? false : only_progress
from
.progress(function(){
var args = $.makeArray(arguments)
prefix != null && args.splice(0, 0, prefix)
to.notify.apply(to, args)
})
if(!only_progress){
from
.done(function(){
var args = $.makeArray(arguments)
to.resolve.apply(to, args)
})
.fail(function(){
var args = $.makeArray(arguments)
prefix != null && args.splice(0, 0, prefix)
to.reject.apply(to, args)
})
}
return from
}
// Semi-generic deferred JSON file loader
//
// If pattern is given, then search for the latest (ordered last) file
// and load that.
// else load the dfl file.
//
// If diff_pattern is given, then merge all matching files in order
// (first to last) with the loaded "main" file
//
// If default_data is then not finding a file will not fail, instead the
// default_data will be the resolved data.
//
// NOTE: if diffs are available this expects the file to contain an object,
// and will extend ($.extend(..)) that object.
// NOTE: if neither of dfl, pattern or diff_pattern are given, then this
// is essentially the same as $.getJSON(...)
// NOTE: this needs listDir(...) to search for latest versions of files.
function loadLatestJSONFile(path, dfl, pattern, diff_pattern, default_data, tracker){
var pparts = path.split(/[\/\\]/)
dfl = dfl == null ? pparts.pop() : dfl
//path = path == dfl ? '.' : path
path = pparts.join('/')
var res = $.Deferred()
if(dfl == ''){
return res.reject()
}
// can't find diffs if can't list dirs...
if(window.listDir == null && (pattern != null || diff_pattern != null)){
return res.reject('listDir unsupported.')
}
var file_list = null
// find the latest...
if(pattern != null){
file_list = listDir(path)
if(file_list == null){
return res.reject()
}
pattern = RegExp(pattern)
var file = $.map(file_list, function(e){
return pattern.test(e) ? e : null
}).sort().reverse()[0]
}
var file = file == null ? dfl : file
var base_date = file.split('-')[0]
base_date = base_date == file ? '' : base_date
var diff_data = {}
var diff = true
// collect and merge diffs...
if(diff_pattern != null){
file_list = file_list == null ? listDir(path) : file_list
if(file_list == null){
return res.reject()
}
diff_pattern = RegExp(diff_pattern)
var diff_data = [diff_data]
var diffs_names = $.map(file_list, function(e){
return diff_pattern.test(e) && e.split('-')[0] >= base_date ? e : null
}).sort()
diff = $.when.apply(null, $.map(diffs_names, function(e, i){
var getter = $.getJSON(path +'/'+ e)
.done(function(data){
// NOTE: we are not using push here so as to
// keep the sort order...
diff_data[i+1] = data
})
.always(function(){
res.notify(e, getter)
})
tracker != null && tracker.notify(e, getter)
return getter
}))
// merge the diffs...
// NOTE: .then(...) handlers get different signature args
// depending on the number of arguments to .when(...)...
.then(function(){
$.extend.apply(null, diff_data)
diff_data = diff_data[0]
})
}
// load the main file and merge the diff with it...
var getter = $.getJSON(path +'/'+ file)
.always(function(){
res.notify(file, getter)
tracker != null && tracker.notify(file, getter)
})
$.when(diff, getter)
.done(function(_, json){
json = json[0]
// merge diffs...
if(Object.keys(diff_data).length != 0){
$.extend(json, diff_data)
}
res.resolve(json)
})
.fail(function(){
if(default_data != null){
res.resolve(default_data)
} else {
res.reject()
}
})
return res
}
/**********************************************************************
* File serialization framework...
*
* This helps us define:
* - named data type loader -- makeFileLoader(..)
* - support different file versions -- load latest
* - support a specific directory structure and centralized
* configuration
* - named data type saver -- makeFileSaver(..)
* - save a version
* - support a specific directory structure and centralized
* configuration
* - a way to mark named data type as modified -- fileUpdated(..)
* - a way to load all registered data types -- runFileLoaders(..)
* - a way to save modified data types -- runFileSavers(..)
*
* NOTE: saving/loading a diff is done manually, see:
* - saveFileImages(..)
* - saveFileImagesDiff(..)
* - loadFileImages(..)
*/
// Construct a JSON file loader...
//
// This will:
// - create configured file loader
// - register the loader in the FILE_LOADERS collection, unless
// skip_reg is true
//
// The loader will:
// - get all the config data from CONFIG,
// - load the file name from cache dir (see: loadLatestJSONFile(..)),
// - if file does not exist use default_data
// - call set_data to setup read data,
// - trigger the evt_name event on the viewer when done,
// - on load error call error function if given,
// - report progress (see: bubbleProgress(..))
//
//
// Also see:
// makeFileSaver(..)
// runFileLoaders(..)
// runFileSavers(..)
// fileUpdated(..)
//
// NOTE: config change to name will not affect this...
function makeFileLoader(title, name, default_data, set_data, error, evt_name, skip_reg){
var _loader = function(path, tracker){
var res = $.Deferred()
// NOTE: these are static!!
var file_dfl = makeBaseFilename(name)
var file_pattern = makeFilenamePattern(name)
// default locations...
if(path == null){
var base = normalizePath(CONFIG.cache_dir_var)
var loader = loadLatestJSONFile(base,
file_dfl,
file_pattern,
null,
default_data,
tracker)
// explicit path...
} else {
path = normalizePath(path)
var base = path.split(CONFIG.cache_dir)[0]
base = base +'/'+ CONFIG.cache_dir
var loader = loadLatestJSONFile(base,
file_dfl,
file_pattern,
null,
default_data,
tracker)
}
res.done(set_data)
bubbleProgress(title, loader, res)
if(error != null){
res.fail(error)
}
if(evt_name != null){
res.done(function(){ $('.viewer').trigger(evt_name) })
}
return res
}
if(!skip_reg){
FILE_LOADERS.push(_loader)
}
return _loader
}
// XXX make this check for updates -- no need to re-save if nothing
// changed...
function makeFileSaver(title, file_dfl, get_data, skip_reg){
var _saver = function(path, date){
path = path == null
? normalizePath(CONFIG.cache_dir_var +'/'+ makeFilename(file_dfl, date))
: path
dumpJSON(path, get_data())
}
if(!skip_reg){
FILE_SAVERS[title] = _saver
}
return _saver
}
// Mark file type as updated...
//
function fileUpdated(name){
if(FILES_UPDATED.indexOf(name) < 0 && name in FILE_SAVERS){
FILES_UPDATED.push(name)
}
}
// NOTE: this will bubble progress to next automatically...
function runFileLoaders(prefix, next, tracker){
FILES_UPDATED = []
return $.when.apply(null, FILE_LOADERS.map(function(load){
return bubbleProgress(prefix, load(null, tracker), next, true)
}))
}
// NOTE: if all is set, this will force save everything...
// XXX do we need bubbleProgress(..) here???
function runFileSavers(path, date, all){
var updated = FILES_UPDATED
FILES_UPDATED = []
for(var n in FILE_SAVERS){
if(all || updated.indexOf(n) >= 0){
showStatusQ('Saving: File:', n)
FILE_SAVERS[n](path, date)
}
}
}
/************************************************************ Data ***/
// XXX should this be here or in data.js???
// NOTE: this is not registered in file loaders as it needs to be called
// explicitly before anything else is loaded...
// NOTE: after v2.2 this will not load the current position as it's stored
// in a separate file (current.json), see: loadFileState(..) for
// details...
var loadFileData = makeFileLoader(
'Data',
CONFIG.data_file,
null,
function(data){
// legacy format...
if(data.version == null){
json = convertDataGen1(data)
DATA = data.data
IMAGES = data.images
MARKED = []
// version 2.*
} else if(/2\.[0-9]*/.test(data.version)) {
DATA = data
}
},
null,
null,
// stop from registering in FILE_LOADERS...
true)
var saveFileData = makeFileSaver(
'Data',
CONFIG.data_file,
function(){
var data = getAllData()
data.current = DATA.current
return data
})
// NOTE: this will set the updated flag ONLY of out of cropped mode...
function dataUpdated(){
if(!isViewCropped()){
fileUpdated('Data')
$('.viewer').trigger('dataUpdated')
}
}
/********************************************************** Images ***/
// Load images from file
//
// This will also merge all diff files.
function loadFileImages(path, tracker, no_load_diffs){
no_load_diffs = window.listDir == null ? true : no_load_diffs
var res = $.Deferred()
// default locations...
if(path == null){
var base = normalizePath(CONFIG.cache_dir_var)
var loader = loadLatestJSONFile(base,
makeBaseFilename(CONFIG.images_file),
makeFilenamePattern(CONFIG.images_file),
makeDiffFilePattern(CONFIG.images_file),
null,
tracker)
// explicit base dir...
} else if(!/\.json$/i.test(path)) {
var base = normalizePath(path +'/'+ CONFIG.cache_dir_var)
var loader = loadLatestJSONFile(base,
makeBaseFilename(CONFIG.images_file),
makeFilenamePattern(CONFIG.images_file),
makeDiffFilePattern(CONFIG.images_file),
null,
tracker)
// explicit path...
} else {
var loader = loadLatestJSONFile(normalizePath(path), null, null, null, null, tracker)
}
bubbleProgress('Images', loader, res)
res.done(function(images){
IMAGES = images
IMAGES_UPDATED = []
IMAGES_CREATED = false
// XXX is this the correct spot to do this???
$('.viewer').trigger('imagesLoaded')
})
return res
}
// Save current images list to file
//
var saveFileImages = makeFileSaver(
'Images',
CONFIG.images_file,
function(){
IMAGES_UPDATED = []
IMAGES_CREATED = false
return IMAGES
},
true)
function saveFileImagesAndRemoveDiffs(){
// save...
saveFileImages()
// remove...
var diff_pattern = makeDiffFilePattern(CONFIG.images_file)
$.each($.map(listDir(normalizePath(CONFIG.cache_dir_var)), function(e){
return diff_pattern.test(e) ? e : null
}), function(i, e){
showStatusQ('Removeing:', e)
removeFile(normalizePath(CONFIG.cache_dir_var +'/'+ e))
})
}
function saveFileImagesDiff(name, date){
var path = normalizePath(CONFIG.cache_dir_var)
name = name == null
? normalizePath(path +'/'+ makeDiffFilename(CONFIG.images_file, date))
: name
var updated = {}
$.each(IMAGES_UPDATED, function(i, e){
updated[e] = IMAGES[e]
})
dumpJSON(name, updated)
IMAGES_UPDATED = []
}
/**********************************************************************
* Global loaders/savers
*/
// Load images, ribbons and run registered load callbacks...
//
// XXX add support for explicit filenames...
function loadFileState(path, prefix, tracker){
prefix = prefix == null ? 'Data' : prefix
prefix = prefix === false ? null : prefix
// XXX explicit data file path...
if(/\.json$/i.test(path)){
// XXX at this
var base = path.split(CONFIG.cache_dir)[0]
base = base == path ? '.' : base
} else {
var base = path.split(CONFIG.cache_dir)[0]
base = base == path ? '.' : base
}
var res = $.Deferred()
// XXX load config...
bubbleProgress(prefix,
loadFileData(path, tracker),
res,
true)
.done(function(json){
setBaseURL(base)
// load the rest of the files...
$.when(
// load current position...
// added on 2.2
bubbleProgress(prefix,
loadLatestJSONFile(path,
makeBaseFilename(CONFIG.current_file),
null,
null,
DATA.current,
tracker),
res,
true)
.done(function(cur){
DATA.current = cur
}),
// load images...
bubbleProgress(prefix,
loadFileImages(base, tracker), res, true),
// run registered loaders...
// added on 2.1
runFileLoaders(prefix, res, tracker))
.done(function(){
$('.viewer').trigger('fileStateLoaded')
reloadViewer()
res.resolve()
})
// XXX fail???
})
.fail(function(){
res.reject('Loading', path, 'Error')
})
return res
}
// Save, ribbons and run registered save callbacks...
//
// NOTE: this will NOT save images, that operation must be explicitly
// performed by saveFileImages(...)
//
// XXX do propper reporting...
// XXX check if name is needed and how it works...
function saveFileState(name, no_normalize_path){
var date = Date.timeStamp()
if(!no_normalize_path){
var path = normalizePath(CONFIG.cache_dir_var)
}
var cur_file = makeBaseFilename(CONFIG.current_file)
showStatusQ('Saving: File:', cur_file)
// allways update the current position...
dumpJSON(path +'/'+ cur_file, DATA.current)
// save created images...
if(IMAGES_CREATED){
showStatusQ('Saving: File: Images.')
saveFileImages(name, date)
// save the updated images...
} else if(IMAGES_UPDATED.length > 0){
showStatusQ('Saving: File: Images diff.')
saveFileImagesDiff(name, date)
}
// save the rest of the data...
runFileSavers(name, date)
}
// Load a directory as-is
//
// XXX check if we need to pass down sorting settings to the generators...
function loadRawDir(path, no_preview_processing, prefix){
prefix = prefix == null ? 'Data' : prefix
prefix = prefix === false ? null : prefix
var files = listDir(path)
var res = $.Deferred()
// filter images...
var image_paths = $.map(files, function(e){
return IMAGE_PATTERN.test(e) ? e : null
})
if(image_paths.length == 0){
// no images in path...
res.notify(prefix, 'Load', path, 'Error')
return res.reject()
}
setBaseURL(path)
IMAGES = imagesFromUrls(image_paths)
IMAGES_CREATED = true
res.notify(prefix, 'Loaded', 'Images.')
DATA = dataFromImages(IMAGES)
dataUpdated()
res.notify(prefix, 'Loaded', 'Data.')
// XXX this will reload viewer...
//updateRibbonsFromFavDirs()
DATA.ribbons = ribbonsFromFavDirs(null, null, imageOrderCmp)
res.notify(prefix, 'Loaded', 'Fav dirs.')
MARKED = []
reloadViewer()
var _run = function(func){
var res = $.Deferred()
func()
.done(function(){
res.resolve()
})
return res
}
// load/generate previews...
if(!no_preview_processing){
var p = _run(makeImagesPreviewsQ)
} else {
var p = $.Deferred().resolve()
}
// read orientation form files...
res.notify(prefix, 'Loading', 'Images metadata.')
var o = $.when(
p,
_run(readImagesOrientationQ),
_run(readImagesDatesQ),
// XXX this is still experimental...
// ...not sure if this will not cause race conditions with
// the other readers...
_run(updateImagesGIDsQ)
)
.done(function(){
res.notify(prefix, 'Loaded', 'Images metadata.')
//updateImagesGIDsQ()
})
// NOTE: we are not waiting for previews and orientation...
return res.resolve()
}
// Load a path
//
// This will try and do one of the following in order:
// 1) look for a cache and load it,
// 2) load data from within the directory
// 3) load a directory as-is
// load fav dirs
//
// NOTE: this will create an images.json file in cache on opening an
// un-cached dir (XXX is this correct???)
// NOTE: if tracker === false no tracker will get created...
function loadDir(path, no_preview_processing, prefix, tracker){
prefix = prefix == null ? 'Data' : prefix
prefix = prefix === false ? null : prefix
var res = $.Deferred()
if(tracker == null){
var tracker = statusProgress('Loading')
// XXX is this the right way to go???
res
.done(function(){
tracker.resolve()
})
.fail(function(){
tracker.reject()
})
}
if(tracker == false){
tracker == null
}
// stop all workers running on current image set before we
// move to the next...
// XXX is this the correct spot for this???
killAllWorkers()
IMAGES_CREATED = false
path = normalizePath(path)
var orig_path = path
var data
res.notify(prefix, 'Loading', path)
var files = listDir(path)
if(files == null){
//showErrorStatus('No files in path: ' + path)
res.notify('load_error', path)
return res.reject()
}
// see if there is a cache...
if(files.indexOf(CONFIG.cache_dir) >= 0){
path = path +'/'+ CONFIG.cache_dir
}
bubbleProgress(prefix,
loadFileState(path, false, tracker), res, true)
.done(function(){
res.resolve()
})
.fail(function(){
bubbleProgress('Raw directory',
loadRawDir(orig_path, no_preview_processing), res)
})
return res
}
// Construct a ribbons hierarchy from the fav dirs structure
//
// NOTE: this depends on listDir(...)
// NOTE: this assumes that images contain ALL the images...
// NOTE: this assumes that all file names are unique...
function ribbonsFromFavDirs(path, images, cmp, dir_name){
path = path == null ? getBaseURL() : path
images = images == null ? IMAGES : images
dir_name = dir_name == null ? 'fav' : dir_name
// build a reverse name-gid index for fast access...
var index = {}
var name
for(var gid in images){
name = getImageFileName(gid)
// XXX we assume that names are unique...
index[name] = gid
}
var ribbons = []
// add the base row...
var base = Object.keys(images)
ribbons.push(base)
var files = listDir(path)
var cur_path = path
while(files.indexOf(dir_name) >= 0){
cur_path += '/' + dir_name
files = listDir(cur_path)
ribbon = []
// collect the images...
$.each(files, function(i, e){
var _gid = index[e]
// skip files not in index...
// NOTE: we do not need to filter the files by name as we
// trust the index...
if(_gid == null){
return
}
// remove the found item from each of the below ribbons...
$.each(ribbons, function(i ,e){
if(e.indexOf(_gid) != -1){
e.splice(e.indexOf(_gid), 1)
}
})
ribbon.push(_gid)
})
ribbons.push(ribbon)
}
// remove empty ribbons and sort the rest...
ribbons = $.map(ribbons, function(e){
return e.length > 0 ? [cmp == null ? e : e.sort(cmp)] : null
})
return ribbons.reverse()
}
// Load ribbon structure from fav directory tree
//
// XXX loads duplicate images....
function updateRibbonsFromFavDirs(){
DATA.ribbons = ribbonsFromFavDirs(null, null, imageOrderCmp)
sortImagesByDate()
reloadViewer()
dataUpdated()
}
function exportImageTo(gid, path, im_name, size){
path = path == null ? BASE_URL : path
im_name = im_name == null ? '%f' : im_name
size = size == null ? 1000 : size
// get correct preview...
var src = getBestPreview(gid, size).url
var orig = getImageFileName(gid)
// XXX might be a good idea to combine this with docs as a
// single mechanism...
// form image name...
var dest = im_name
// full filename...
dest = dest.replace('%f', orig)
// file name w.o. ext...
dest = dest.replace('%n', orig.split('.')[0])
// ext...
dest = dest.replace('%e', '.'+src.split('.').pop())
// marked status...
dest = dest.replace(/%\(([^)]*)\)m/, MARKED.indexOf(gid) >= 0 ? '$1' : '')
// bookmarked status...
dest = dest.replace(/%\(([^)]*)\)b/, BOOKMARKS.indexOf(gid) >= 0 ? '$1' : '')
// gid...
dest = dest.replace('%gid', gid)
dest = dest.replace('%g', gid.slice(34))
dest = path +'/'+ dest
// copy...
return (function(src, dest){
return copyFile(src, dest)
.done(function(){
// XXX do we actually need this???
console.log(src, 'done.')
})
.fail(function(err){
// XXX do we actually need this???
console.warn(src, 'err:', err)
})
})(src, dest)
}
// Export current state to directory...
//
// XXX this copies the files in parallel, make it sync and sequential...
// ...reason is simple, if we stop the copy we need to end up with
// part of the files copied full rather than all partially...
function exportImagesTo(path, im_name, dir_name, size){
path = path == null ? BASE_URL : path
im_name = im_name == null ? '%f' : im_name
dir_name = dir_name == null ? 'fav' : dir_name
size = size == null ? 1000 : size
var res = $.Deferred()
var base_path = path
path = normalizePath(path)
var order = DATA.order
var Z = (('10e' + (order.length + '').length) * 1 + '').slice(2)
// mainly used for file naming, gives us ability to number images
// in the current selection...
var selection = []
$.each(DATA.ribbons, function(_, e){
selection = selection.concat(e)
})
selection.sort(imageOrderCmp)
var z = (('10e' + (selection.length + '').length) * 1 + '').slice(2)
// use an external pool...
//var pool = makeDeferredPool()
var pool = getWorkerQueue('Export previews', 64)
.depleted(function(){
showStatusQ('Export: done.')
res.resolve()
})
pool.filling()
// go through ribbons...
for(var i=DATA.ribbons.length-1; i >= 0; i--){
var ribbon = DATA.ribbons[i]
// go through images...
for(var j=0; j < ribbon.length; j++){
var gid = ribbon[j]
// do global naming...
var dest = im_name
// global order...
var o = order.indexOf(gid) + ''
dest = dest.replace('%I', (Z + o).slice(o.length))
// current order...
var o = selection.indexOf(gid) + ''
dest = dest.replace('%i', (z + o).slice(o.length))
pool.enqueue(exportImageTo, gid, path, dest, size)
}
path = normalizePath(path +'/'+ dir_name)
}
pool.doneFilling()
return res
}
/**********************************************************************
* Metadata readers...
*/
// NOTE: this will overwrite current image orientation...
//
// XXX this depends on getImageOrientation(...)
function readImageOrientation(gid, no_update_loaded){
gid = gid == null ? getImageGID() : gid
var img = IMAGES[gid]
if(img == null){
return
}
return getImageOrientation(normalizePath(img.path))
.done(function(o){
var o_o = img.orientation
var o_f = img.flipped
img.orientation = o.orientation
img.flipped = o.flipped
// mark image dirty...
if(o_o != o.orientation || o_f != o.flipped){
imageUpdated(gid)
}
// update image if loaded...
if(!no_update_loaded){
var o = getImage(gid)
if(o.length > 0){
updateImage(o)
}
}
})
}
function readImagesOrientation(gids, no_update_loaded){
gids = gids == null ? getClosestGIDs() : gids
var res = []
$.each(gids, function(_, gid){
res.push(readImageOrientation(gid, no_update_loaded))
})
return $.when.apply(null, res)
}
// queued version of readImagesOrientation(...)
//
function readImagesOrientationQ(gids, no_update_loaded){
gids = gids == null ? getClosestGIDs() : gids
var queue = getWorkerQueue('Read images orientation', 4)
var last = null
// attach workers to queue...
$.each(gids, function(_, gid){
last = queue.enqueue(readImageOrientation, gid, no_update_loaded)
})
return queue
}
function readImageDate(gid, images){
images = images == null ? IMAGES : images
var img = images[gid]
return getEXIFDate(normalizePath(img.path))
.done(function(date){
img.ctime = Date.fromTimeStamp(date).getTime()/1000
imageUpdated(gid)
})
}
function readImagesDates(images){
images = images == null ? IMAGES : images
return $.when.apply(null, $.map(images, function(_, gid){
return readImageDate(gid, images)
}))
}
function readImagesDatesQ(images){
images = images == null ? IMAGES : images
var queue = getWorkerQueue('Read images dates', 4)
$.each(images, function(gid, img){
queue.enqueue(readImageDate, gid, images)
})
return queue
}
function updateImageGID(gid, images, data){
gid = gid == null ? getImageGID() : gid
images = images == null ? IMAGES : images
data = data == null ? DATA : data
var img = images[gid]
var key = gid
return getEXIFGID(normalizePath(img.path))
.done(function(gid){
if(gid == img.id){
return
}
img.id = gid
// images...
images[gid] = images[key]
delete images[key]
imageUpdated(gid)
// data...
if(data != null){
// replace current...
if(data.current == key){
data.current = gid
}
// replace in order...
data.order[data.order.indexOf(key)] = gid
// replace in ribbons...
for(var i=0; i < data.ribbons.length; i++){
var r = data.ribbons[i]
var k = r.indexOf(key)
if(k >= 0){
r[k] = gid
}
}
dataUpdated()
}
// update the actual image...
var i = getImage(key)
if(i.length > 0){
updateImage(i, gid)
}
$('.viewer').trigger('updatedImageGID', [key, gid])
})
}
function updateImagesGIDs(images, data){
images = images == null ? IMAGES : images
var res = $.when.apply(null, $.map(images, function(_, key){
return updateImageGID(key, images, data)
}))
IMAGES_CREATED = true
return res
}
function updateImagesGIDsQ(images, data){
images = images == null ? IMAGES : images
var queue = getWorkerQueue('Update GIDs', 4)
$.each(images, function(key){
queue.enqueue(updateImageGID, key, images, data)
})
IMAGES_CREATED = true
return queue
}
/**********************************************************************
* vim:set ts=4 sw=4 : */