Started implementing maintainer action buttons
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
97c2d1dd66
commit
77332428e6
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
|
||||
<link rel="import" href="../bower_components/iron-pages/iron-pages.html">
|
||||
@ -28,7 +29,7 @@
|
||||
@apply --photo-thumbnail;
|
||||
}
|
||||
|
||||
:host > div {
|
||||
#info {
|
||||
position: absolute;
|
||||
padding: 0.5em;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
@ -37,15 +38,29 @@
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
font-size: 0.6em;
|
||||
}
|
||||
}
|
||||
|
||||
:host(:hover) > div {
|
||||
#actions {
|
||||
padding: 0.5em;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
:host(:hover) #info,
|
||||
:host(:hover) #actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="layout vertical start">
|
||||
<div id="actions" hidden$="[[!actions.length]]" class="horziontal layout center end">
|
||||
<template is="dom-repeat" items="[[actions]]">
|
||||
<paper-icon-button on-tap="_fireAction" icon="[[item]]"></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div id="info" class="layout vertical start">
|
||||
<div>[[item.name]] ([[item.id]])</div>
|
||||
<div>[[item.taken]]</div>
|
||||
<div on-tap="_pathTap">[[item.path]]</div>
|
||||
@ -82,6 +97,12 @@
|
||||
"thumbChanged(thumbpath, visible)"
|
||||
],
|
||||
|
||||
_fireAction: function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.fire("action", event.model.item);
|
||||
},
|
||||
|
||||
thumbChanged: function(thumbpath, visible) {
|
||||
if (visible) {
|
||||
this.style.backgroundImage = "url(" +
|
||||
|
@ -221,6 +221,7 @@
|
||||
}
|
||||
|
||||
photo-thumbnail {
|
||||
transition: opacity 0.25s ease-in-out;
|
||||
--photo-thumbnail: {
|
||||
border: 3px solid white;
|
||||
};
|
||||
@ -1212,6 +1213,14 @@
|
||||
|
||||
var thumbnail = document.createElement("photo-thumbnail");
|
||||
thumbnail.item = photo;
|
||||
if (this.user.maintainer) {
|
||||
let actions = [ "delete" ];
|
||||
if (this.mode == "duplicates") {
|
||||
actions.unshift("text-format");
|
||||
}
|
||||
thumbnail.actions = actions;
|
||||
thumbnail.addEventListener("action", this._imageAction.bind(this));
|
||||
}
|
||||
thumbnail.addEventListener("load-image", this._imageTap.bind(this));
|
||||
thumbnail.addEventListener("load-album", this.loadAlbum.bind(this));
|
||||
|
||||
@ -1226,6 +1235,31 @@
|
||||
this.processing = false;
|
||||
},
|
||||
|
||||
_imageAction: function(event) {
|
||||
console.log(event.detail);
|
||||
switch (event.detail) {
|
||||
case "delete": /* Delete image */
|
||||
var thumbnail = event.currentTarget;
|
||||
thumbnail.style.opacity = 0;
|
||||
thumbnail.style.pointerEvents = "none";
|
||||
|
||||
this.async(function(thumbnail) {
|
||||
thumbnail.parentElement.removeChild(thumbnail);
|
||||
for (var i = 0; i < this.thumbnails.length; i++) {
|
||||
if (this.thumbnails[i] == thumbnail) {
|
||||
console.log("Found thumbnail");
|
||||
this.thumbnails.splice(i, 1);
|
||||
this.notifyPath("thumbnails.length");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}.bind(this, thumbnail), 250);
|
||||
break;
|
||||
case "text-format": /* Rename this image */
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
pathTapped: function(event) {
|
||||
this.path = event.currentTarget.path;
|
||||
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
||||
|
@ -27,10 +27,10 @@ function removeNewerFile(path, fileA, fileB) {
|
||||
return;
|
||||
}
|
||||
if (statsA.mtime > statsB.mtime) {
|
||||
console.log("Removing file by moving to 'corrupt':" + fileA);
|
||||
setStatus("Removing file by moving to 'corrupt':" + fileA);
|
||||
moveCorrupt(path, fileA);
|
||||
} else {
|
||||
console.log("Removing file by moving to 'corrupt':" + fileB);
|
||||
setStatus("Removing file by moving to 'corrupt':" + fileB);
|
||||
moveCorrupt(path, fileB);
|
||||
}
|
||||
});
|
||||
@ -101,14 +101,14 @@ const exists = function(path) {
|
||||
}
|
||||
|
||||
function convertRawToJpg(path, file) {
|
||||
console.log("Converting " + path + file);
|
||||
setStatus("Converting " + path + file);
|
||||
|
||||
path = picturesPath + path;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
return exists(path + file.replace(rawExtension, ".jpg")).then(function(exist) {
|
||||
if (exist) {
|
||||
console.log("Skipping already converted file: " + file);
|
||||
setStatus("Skipping already converted file: " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -133,25 +133,25 @@ function convertRawToJpg(path, file) {
|
||||
ufraw.on('exit', function(stderr, code, signal) {
|
||||
if (signal || code != 0) {
|
||||
let error = "UFRAW for " + path + file + " returned an error: " + code + "\n" + signal + "\n" + stderr.join("\n") + "\n";
|
||||
console.error(error);
|
||||
setStatus(error, "error");
|
||||
return moveCorrupt(path, file).then(function() {
|
||||
console.warn("ufraw failed");
|
||||
setStatus("ufraw failed", "warn");
|
||||
return reject(error);
|
||||
}).catch(function(error) {
|
||||
console.warn("moveCorrupt failed");
|
||||
setStatus("moveCorrupt failed", "warn");
|
||||
return reject(error);
|
||||
});
|
||||
}
|
||||
return mkdir(path + "raw").then(function() {
|
||||
fs.rename(path + file, path + "raw/" + file, function(err) {
|
||||
if (err) {
|
||||
console.error("Unable to move RAW file: " + path + file);
|
||||
setStatus("Unable to move RAW file: " + path + file, "error");
|
||||
return reject(err);
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
}).catch(function(error) {
|
||||
console.warn("mkdir failed");
|
||||
setStatus("mkdir failed", "warn");
|
||||
return reject(error);
|
||||
});
|
||||
}.bind(this, ufraw));
|
||||
@ -165,13 +165,13 @@ function moveCorrupt(path, file) {
|
||||
path = picturesPath + path;
|
||||
}
|
||||
|
||||
console.warn("Moving corrupt file '" + file + "' to " + path + "corrupt");
|
||||
setStatus("Moving corrupt file '" + file + "' to " + path + "corrupt", "warn");
|
||||
|
||||
return mkdir(path + "corrupt").then(function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.rename(path + file, path + "corrupt/" + file, function(err) {
|
||||
if (err) {
|
||||
console.error("Unable to move corrupt file: " + path + file);
|
||||
setStatus("Unable to move corrupt file: " + path + file, "error");
|
||||
return reject(err);
|
||||
}
|
||||
return resolve();
|
||||
@ -202,7 +202,7 @@ function processBlock(items) {
|
||||
});
|
||||
|
||||
let toProcess = processing.length, lastMessage = moment();
|
||||
console.log("Items to be processed: " + toProcess);
|
||||
setStatus("Items to be processed: " + toProcess);
|
||||
return Promise.mapSeries(processing, function(asset) {
|
||||
return computeHash(picturesPath + asset.album.path + asset.filename).then(function(hash) {
|
||||
asset.hash = hash;
|
||||
@ -222,7 +222,7 @@ function processBlock(items) {
|
||||
} else if (results[0].hash != asset.hash) {
|
||||
query = "UPDATE photohashes SET hash=:hash WHERE photoId=:id";
|
||||
} else if (results[0].photoId != asset.id) {
|
||||
console.log("Duplicate asset: " +
|
||||
setStatus("Duplicate asset: " +
|
||||
"'" + asset.album.path + asset.filename + "' is a copy of " +
|
||||
"'" + results[0].path + results[0].filename + "'");
|
||||
duplicates.push(asset);
|
||||
@ -293,7 +293,7 @@ function processBlock(items) {
|
||||
asset.modified = moment(metadata.exif.exif.DateTimeOriginal).format();
|
||||
|
||||
if (asset.taken == "Invalid date" || asset.taken.replace(/T.*/, "") == "1899-11-30") {
|
||||
console.log("Invalid EXIF date information for " + asset.album.path + asset.filename);
|
||||
setStatus("Invalid EXIF date information for " + asset.album.path + asset.filename);
|
||||
asset.taken = asset.modified = moment(created).format();
|
||||
}
|
||||
} else {
|
||||
@ -332,7 +332,7 @@ function processBlock(items) {
|
||||
}
|
||||
|
||||
return image.resize(256, 256).toFile(dst).catch(function(error) {
|
||||
console.error("Error resizing image: " + dst, error);
|
||||
setStatus("Error resizing image: " + dst + "\n" + error, "error");
|
||||
throw error;
|
||||
});
|
||||
}).then(function() {
|
||||
@ -343,7 +343,7 @@ function processBlock(items) {
|
||||
}
|
||||
|
||||
return image.resize(Math.min(1024, metadata.width)).toFile(dst).catch(function(error) {
|
||||
console.error("Error resizing image: " + dst, error);
|
||||
setStatus("Error resizing image: " + dst + "\n" + error, "error");
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
@ -355,7 +355,7 @@ function processBlock(items) {
|
||||
});
|
||||
});
|
||||
}).catch(function(error) {
|
||||
console.error("Error reading image " + src + ": ", error);
|
||||
setStatus("Error reading image " + src + ":\n" + error, "error");
|
||||
return moveCorrupt(path, file).then(function() {
|
||||
|
||||
/* If the original file was not a NEF/ORF, we are done... */
|
||||
@ -369,31 +369,31 @@ function processBlock(items) {
|
||||
/* Move the NEF/ORF to /corrupt as well so it isn't re-checked again and again... */
|
||||
// return moveCorrupt(path, asset.filename);
|
||||
|
||||
console.error("Already attempted to convert NEF/ORF to JPG: " + path + file);
|
||||
setStatus("Already attempted to convert NEF/ORF to JPG: " + path + file, "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn("Adding " + path + file + " back onto processing queue.");
|
||||
setStatus("Adding " + path + file + " back onto processing queue.", "error");
|
||||
triedClean.push(path + file);
|
||||
processBlock([ path, file, created, albumId ]);
|
||||
});
|
||||
});
|
||||
}).catch(function() {
|
||||
console.warn("Continuing file processing.");
|
||||
setStatus("Continuing file processing.", "warn");
|
||||
});
|
||||
}).then(function() {
|
||||
toProcess--;
|
||||
if (moment().add(-5, 'seconds') > lastMessage) {
|
||||
console.log("Items to be processed: " + toProcess);
|
||||
setStatus("Items to be processed: " + toProcess);
|
||||
lastMessage = moment();
|
||||
}
|
||||
});
|
||||
}).catch(function(error) {
|
||||
console.log("Error processing file. Continuing.");
|
||||
setStatus("Error processing file. Continuing.");
|
||||
throw error;
|
||||
}).then(function() {
|
||||
console.log("Completed processing queue. Marking " + duplicates.length + " duplicates.");
|
||||
setStatus("Completed processing queue. Marking " + duplicates.length + " duplicates.");
|
||||
let dups = [];
|
||||
duplicates.forEach(function(asset) {
|
||||
/* If not already marked as a duplicate, mark it. */
|
||||
@ -412,7 +412,7 @@ function processBlock(items) {
|
||||
}
|
||||
});
|
||||
}).then(function() {
|
||||
console.log("Looking for removed assets");
|
||||
setStatus("Looking for removed assets");
|
||||
return photoDB.sequelize.query("SELECT photos.scanned,photos.id,photos.filename,albums.path FROM photos " +
|
||||
"LEFT JOIN albums ON (albums.id=photos.albumId) " +
|
||||
"WHERE photos.deleted=0 AND DATETIME(photos.scanned)<DATETIME(:lastScan)", {
|
||||
@ -422,11 +422,11 @@ function processBlock(items) {
|
||||
type: photoDB.sequelize.QueryTypes.SELECT
|
||||
}).then(function(results) {
|
||||
let deleted = [];
|
||||
console.log("Checking " + results.length + " assets to see if they are on disk.");
|
||||
setStatus("Checking " + results.length + " assets to see if they are on disk.");
|
||||
return Promise.map(results, function(asset) {
|
||||
return exists(asset.path + asset.filename).then(function(exist) {
|
||||
if (!exist) {
|
||||
console.log(asset.path + asset.filename + " no longer exists on disk. Marking as deleted.");
|
||||
setStatus(asset.path + asset.filename + " no longer exists on disk. Marking as deleted.");
|
||||
deleted.push(asset.id);
|
||||
}
|
||||
});
|
||||
@ -442,7 +442,7 @@ function processBlock(items) {
|
||||
}
|
||||
});
|
||||
}).then(function() {
|
||||
console.log(deleted.length + " assets deleted.");
|
||||
setStatus(deleted.length + " assets deleted.");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -464,7 +464,7 @@ function scanDir(parent, path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.readdir(path, function(error, files) {
|
||||
if (error) {
|
||||
console.warn("Could not readdir: " + path);
|
||||
setStatus("Could not readdir: " + path, "warn");
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
@ -480,7 +480,7 @@ function scanDir(parent, path) {
|
||||
* version from disk. */
|
||||
if (file != files[i] && file.toUpperCase() == files[i]) {
|
||||
removeNewerFile(path, file, files[i]);
|
||||
console.log("Duplicate file in " + path + ": ", file, files[i]);
|
||||
setStatus("Duplicate file in " + path + ": ", file, files[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -502,7 +502,7 @@ function scanDir(parent, path) {
|
||||
albums = albums.concat(_albums);
|
||||
assets = assets.concat(_assets);
|
||||
}).catch(function(error) {
|
||||
console.warn("Could not scanDir " + filepath + ": " + error);
|
||||
setStatus("Could not scanDir " + filepath + ": " + error, "error");
|
||||
});
|
||||
}
|
||||
|
||||
@ -544,7 +544,7 @@ function findOrCreateDBAlbum(transaction, album) {
|
||||
} else {
|
||||
if (!album.parent.id) {
|
||||
let error = "Albums in array in non ancestral order!";
|
||||
console.error(error);
|
||||
setStatus(error, "error");
|
||||
throw error;
|
||||
}
|
||||
album.parentId = album.parent.id;
|
||||
@ -557,7 +557,7 @@ function findOrCreateDBAlbum(transaction, album) {
|
||||
}).then(function(results) {
|
||||
if (results.length == 0) {
|
||||
if (!album.parent) {
|
||||
console.warn("Creating top level album: " + picturesPath);
|
||||
setStatus("Creating top level album: " + picturesPath, "warn" );
|
||||
}
|
||||
return photoDB.sequelize.query("INSERT INTO albums (path,parentId,name) VALUES(:path,:parentId,:name)", {
|
||||
replacements: album,
|
||||
@ -577,7 +577,7 @@ function findOrCreateDBAlbum(transaction, album) {
|
||||
function findOrUpdateDBAsset(transaction, asset) {
|
||||
if (!asset.album || !asset.album.id) {
|
||||
let error = "Asset being processed without an album";
|
||||
console.error(error);
|
||||
setStatus(error, "warn");
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -607,7 +607,7 @@ function findOrUpdateDBAsset(transaction, asset) {
|
||||
/* If the size on disk changed, update the size entry in the DB. This shouldn't happen in
|
||||
* production unless someone modifies the file, then re-stamps the modified time */
|
||||
if (asset.size != results[0].size) {
|
||||
console.log("File was modified with time-restamp (HASH regeneration will be queued): " + asset.filename);
|
||||
setStatus("File was modified with time-restamp (HASH regeneration will be queued): " + asset.filename);
|
||||
delete asset.scanned;
|
||||
delete asset.modified;
|
||||
}
|
||||
@ -638,7 +638,30 @@ function computeHash(filepath) {
|
||||
});
|
||||
}
|
||||
|
||||
let scanning = false;
|
||||
let scanningStatus = [];
|
||||
|
||||
function setStatus(status, level) {
|
||||
if (status == "idle") {
|
||||
scanningStatus = [];
|
||||
}
|
||||
level = level || "info";
|
||||
scanningStatus.push({
|
||||
level: level,
|
||||
time: moment().format(),
|
||||
log: status
|
||||
});
|
||||
switch (level) {
|
||||
case "error":
|
||||
console.error(status);
|
||||
break;
|
||||
case "warn":
|
||||
console.warn(status);
|
||||
break;
|
||||
default:
|
||||
console.log(status);
|
||||
}
|
||||
}
|
||||
|
||||
function doScan() {
|
||||
/* 1. Scan for all assets which will be managed by the system. readdir
|
||||
* 2. Check if entry in DB. Check mod-time in DB vs. stats from #1
|
||||
@ -659,12 +682,12 @@ function doScan() {
|
||||
let now = Date.now();
|
||||
let needsProcessing = [];
|
||||
|
||||
if (scanning) {
|
||||
return Promise.resolve("scanning");
|
||||
if (scanningStatus.length != 0) {
|
||||
return Promise.resolve(scanningStatus);
|
||||
}
|
||||
|
||||
return scanDir(null, picturesPath).spread(function(albums, assets) {
|
||||
console.log("Found " + assets.length + " assets in " + albums.length + " albums after " +
|
||||
setStatus("Found " + assets.length + " assets in " + albums.length + " albums after " +
|
||||
((Date.now() - now) / 1000) + "s");
|
||||
/* One at a time, in series, as the album[] array has parents first, then descendants.
|
||||
* Operating in parallel could result in a child being searched for prior to the parent */
|
||||
@ -676,17 +699,17 @@ function doScan() {
|
||||
return findOrCreateDBAlbum(transaction, album).then(function() {
|
||||
toProcess--;
|
||||
if (moment().add(-5, 'seconds') > lastMessage) {
|
||||
console.log("Albums to be created in DB: " + toProcess);
|
||||
setStatus("Albums to be created in DB: " + toProcess);
|
||||
lastMessage = moment();
|
||||
}
|
||||
});
|
||||
});
|
||||
}).then(function() {
|
||||
console.log("Processed " + albums.length + " album DB entries in " +
|
||||
setStatus("Processed " + albums.length + " album DB entries in " +
|
||||
((Date.now() - now) / 1000) + "s");
|
||||
now = Date.now();
|
||||
|
||||
console.log(assets.length + " assets remaining to be verified/updated. ETA N/A");
|
||||
setStatus(assets.length + " assets remaining to be verified/updated. ETA N/A");
|
||||
|
||||
let processed = 0, start = Date.now(), last = 0, updateScanned = [], newEntries = 0;
|
||||
return photoDB.sequelize.transaction(function(transaction) {
|
||||
@ -721,7 +744,7 @@ function doScan() {
|
||||
}
|
||||
|
||||
let remaining = assets.length - processed, eta = Math.ceil((elapsed / 1000) * remaining / (processed - last));
|
||||
console.log(remaining + " assets remaining be verified/updated " +
|
||||
setStatus(remaining + " assets remaining be verified/updated " +
|
||||
"(" + newEntries + " new entries, " + (processed - newEntries) + " up-to-date so far). ETA " + eta + "s");
|
||||
last = processed;
|
||||
start = Date.now();
|
||||
@ -736,23 +759,23 @@ function doScan() {
|
||||
ids: updateScanned
|
||||
}
|
||||
}).then(function() {
|
||||
console.log("Updated scan date of " + updateScanned.length + " assets");
|
||||
setStatus("Updated scan date of " + updateScanned.length + " assets");
|
||||
updateScanned = [];
|
||||
});
|
||||
}
|
||||
}).then(function() {
|
||||
console.log(newEntries + " assets are new. " + (needsProcessing.length - newEntries) + " assets have been modified.");
|
||||
console.log(needsProcessing.length + " assets need HASH computed. " + (assets.length - needsProcessing.length) + " need no update.");;
|
||||
setStatus(newEntries + " assets are new. " + (needsProcessing.length - newEntries) + " assets have been modified.\n" +
|
||||
needsProcessing.length + " assets need HASH computed. " + (assets.length - needsProcessing.length) + " need no update.");;
|
||||
processBlock(needsProcessing);
|
||||
needsProcessing = [];
|
||||
}).then(function() {
|
||||
console.log("Scanned " + assets.length + " asset DB entries in " +
|
||||
setStatus("Scanned " + assets.length + " asset DB entries in " +
|
||||
((Date.now() - now) / 1000) + "s");
|
||||
assets = [];
|
||||
});
|
||||
});
|
||||
}).then(function() {
|
||||
console.log("Total time to initialize DB and all scans: " + ((Date.now() - initialized) / 1000) + "s");
|
||||
setStatus("Total time to initialize DB and all scans: " + ((Date.now() - initialized) / 1000) + "s");
|
||||
return photoDB.sequelize.query("SELECT max(scanned) AS scanned FROM photos", {
|
||||
type: photoDB.sequelize.QueryTypes.SELECT
|
||||
}).then(function(results) {
|
||||
@ -761,11 +784,14 @@ function doScan() {
|
||||
} else {
|
||||
lastScan = new Date(results[0].scanned);
|
||||
}
|
||||
console.log("Updating any asset newer than " + moment(lastScan).format());
|
||||
setStatus("Updating any asset newer than " + moment(lastScan).format());
|
||||
});
|
||||
}).then(function() {
|
||||
scanning = false;
|
||||
setStatus("idle");
|
||||
return "scan complete";
|
||||
}).catch(function(error) {
|
||||
setStatus(error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
@ -773,8 +799,6 @@ module.exports = {
|
||||
init: function(db) {
|
||||
photoDB = db;
|
||||
},
|
||||
scan: function () {
|
||||
return doScan();
|
||||
}
|
||||
scan: doScan
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user