Started implementing maintainer action buttons

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-10-07 16:26:42 -07:00
parent 97c2d1dd66
commit 77332428e6
3 changed files with 136 additions and 57 deletions

View File

@ -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(" +

View File

@ -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 = "";

View File

@ -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
};