Improved slideshow to support ?holiday URL matching

Added docker config

Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
James Ketrenos 2019-11-28 01:54:02 -08:00
parent 55a9c16e7b
commit c2f72df80a
11 changed files with 326 additions and 215 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
*
!entrypoint.sh

1
.env Normal file
View File

@ -0,0 +1 @@
USER_ID=1000:1000

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ node_modules
./elements
frontend/bower_components
pictures
db
*.db

52
Dockerfile Normal file
View File

@ -0,0 +1,52 @@
FROM ubuntu:disco
RUN apt-get update
RUN apt-get install -y wget
# Upgrade Node
RUN wget -qO- https://deb.nodesource.com/setup_10.x | bash -
RUN DEBIAN_FRONTEND=NONINTERACTIVE apt-get install -y \
nodejs \
gcc \
g++ \
make \
cmake
# You can then install the latest npm, polymer-cli, and bower:
RUN npm install --global npm@latest npx
# Speed up face-recognition and dev tools
RUN apt-get install -y libopenblas-dev cmake
# NEF processing uses ufraw-batch
RUN apt-get install -y ufraw-batch
# Create a user with sudo access
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
sudo
# NOTE: Requires 'sudo' package to already be installed
RUN groupadd -g 1000 user \
&& useradd --no-log-init \
-s /bin/bash \
-u 1000 \
-m \
-g user \
-G sudo \
-p $(echo "user" | openssl passwd -stdin) user
# Set 'sudo' to NOPASSWD for all container users
RUN sed -i -e 's,%sudo.*,%sudo ALL=(ALL) NOPASSWD:ALL,g' /etc/sudoers
COPY /entrypoint.sh /entrypoint.sh
RUN DEBIAN_FRONTEND=noninteractive \
&& apt-get install --no-install-recommends -y \
git
USER user
WORKDIR /website
CMD [ "/entrypoint.sh" ]

View File

@ -28,47 +28,6 @@ sudo apt install -y ufraw-batch
### Create `photos` user for DB
You will need to know the root password for your mariadb installation. If you
need to reset the root password, you can perform the following:
```bash
export PASSWORD=m4g1cP4ssw0rd
sudo service mysql stop
sudo mysqld_safe --skip-grant-tables &
sleep 1 # mysqld_safe can take a bit of time to come online
sudo mysql << EOF
use mysql;
UPDATE user SET PASSWORD=PASSWORD("${PASSWORD}") WHERE USER='root';
UPDATE user SET plugin='mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
QUIT
EOF
sudo service mysql stop
sudo service mysql start
```
Once you know the root password, you can then create a new user and DB
for the photo app via:
### Create the USER
```bash
U='photos'
P='p4$$w0rd'
mysql -u root --password=${PASSWORD} << EOF
CREATE USER '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```
### Create the DB 'photos'
```bash
U='photos'
P='p4$$w0rd'
D='photos'
mysql -u root --password=${PASSWORD} << EOF
DROP DATABASE IF EXISTS ${D};
CREATE DATABASE ${D} CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON ${D}.* TO '${U}'@'localhost' IDENTIFIED BY '${P}';
EOF
```
Photos is currently using sqlite, which means you don't need to
do anything fancy beyond having run 'npm install' to get sequelize
and sqlite3 installed.

View File

@ -1,14 +1,14 @@
{
"db": {
"photos": {
"host": "sqlite:photos.db",
"host": "sqlite:db/photos.db",
"options": {
"logging" : false,
"timezone": "+00:00"
}
},
"users": {
"host": "sqlite:users.db",
"host": "sqlite:db/users.db",
"options": {
"logging" : false,
"timezone": "+00:00"

30
docker-compose.yml Normal file
View File

@ -0,0 +1,30 @@
version: '3.1'
services:
# db:
# image: mariadb
# restart: always
# environment:
# MYSQL_ROOT_PASSWORD: photos
# PHOTOS_DB_USER: photos
# PHOTOS_DB_PASSWD: ph0t0z
# PHOTOS_DB: photos
# volumes:
# - ${PWD}/db:/var/lib/mysql
# - ./init.sql:/data/application/init.sql
photos:
user: ${USER_ID}
build: .
image: photos:latest
container_name: photos
# depends_on:
# - db
restart: always
ports:
- 8123:8123
volumes:
- ${PWD}/photos:/photos
- ${PWD}/db:/db
- ${PWD}:/website

5
entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
while true; do
npm start
sleep 3
done

View File

@ -1,51 +1,66 @@
<html>
<script src="src/ketr-photos/fetch.js"></script>
<base href="/">
<base href="/photos/">
<body>
<style>
body {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
#photo {
position: fixed;
display: inline-block;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
background-color: #222;
position: fixed;
display: inline-block;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
background-color: #222;
}
#placeholder {
position: absolute;
left: -1000px;
display: none;
position: absolute;
left: -1000px;
display: none;
}
#footer {
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
opacity: 0.6;
background: linear-gradient(45deg, rgba(16, 16, 16, 1), transparent);
color: white;
font-size: 1.5em;
line-height: 1.5em;
font-family: Arial;
box-shadow: 0px -0.25em 1em black;
text-shadow: 0 1px 0 black;
z-index: 100;
display: flex;
flex-direction: row;
justify-content: space-between;
}
#info {
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
padding: 0.5em 1em;
background-color: #444;
color: white;
font-size: 1.5em;
line-height: 1.5em;
font-family: Arial;
box-shadow: 0px -0.25em 1em black;
padding: 0.5em 1em;
}
#loading {
padding: 0.5em 1em;
}
</style>
<img id="placeholder"></img>
<div id="photo">
</div>
<div id="info">
Loading photoset...
<div id="footer">
<div id="info">Loading photoset...</div>
<div id="loading"></div>
</div>
<script>
var base, placeholder, info, photos = [], photoIndex;
@ -66,27 +81,83 @@ function shuffle(array) {
return array;
}
var countdown = 15;
const days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
function loadPhoto(index) {
var photo = photos[index];
placeholder.src = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename;
const photo = photos[index],
xml = new XMLHttpRequest(),
url = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename,
taken = new Date(photo.taken);
document.getElementById("loading").textContent = "0%";
xml.onprogress = function (event) {
var alpha = 0;
if (event.total) {
alpha = event.loaded / event.total;
document.getElementById("loading").textContent = Math.ceil(100 * alpha) + "%";
} else {
document.getElementById("loading").textContent = "0%";
}
};
xml.onload = function(event) {
info.textContent =
days[taken.getDay()] + ", " +
months[taken.getMonth()] + " " +
taken.getDate() + " " +
taken.getFullYear();
document.getElementById("photo").style.backgroundImage = "url(" + url + ")";
countdown = 15;
tick();
}
xml.onerror = function(event) {
info.textContent = "Error loading photo. Trying next photo.";
nextPhoto();
}
xml.open("GET", url, true);
xml.send();
}
function nextPhoto() {
photoIndex = (photoIndex + 1) % photos.length;
loadPhoto(photoIndex);
photoIndex = (photoIndex + 1) % photos.length;
loadPhoto(photoIndex);
}
var scheduled = false;
function schedule() {
function tick() {
if (scheduled) {
clearTimeout(scheduled);
}
document.getElementById("loading").textContent = countdown + "s";
if (countdown > 0) {
/* If there is a timer running, then decrement the counter */
if (scheduled) {
clearTimeout(scheduled);
countdown--;
}
scheduled = setTimeout(function () {
scheduled = undefined;
nextPhoto();
}, 15 * 1000);
scheduled = setTimeout(tick, 1000);
} else {
scheduled = null;
countdown = 15;
nextPhoto();
}
}
function schedule() {
if (scheduled) {
clearTimeout(scheduled);
}
tick();
}
var paused = false;
document.addEventListener("DOMContentLoaded", function() {
var tmp = document.querySelector("base");
if (tmp) {
@ -96,71 +167,58 @@ document.addEventListener("DOMContentLoaded", function() {
}
document.addEventListener("keydown", function(event) {
if (event.keyCode == 37) { /* left */
if (photoIndex == 0) {
photoIndex = photos.length;
}
photoIndex--;
loadPhoto(photoIndex);
return;
if (event.keyCode == 32) { /* space */
paused = !paused;
if (!paused) {
tick();
} else {
document.getElementById("loading").textContent = "||";
clearTimeout(scheduled);
scheduled = null;
}
return;
}
if (event.keyCode == 39) { /* right */
photoIndex = (photoIndex + 1) % photos.length;
loadPhoto(photoIndex);
return;
if (event.keyCode == 37) { /* left */
if (photoIndex == 0) {
photoIndex = photos.length;
}
photoIndex--;
loadPhoto(photoIndex);
return;
}
console.log(event.keyCode);
if (event.keyCode == 39) { /* right */
photoIndex = (photoIndex + 1) % photos.length;
loadPhoto(photoIndex);
return;
}
console.log(event.keyCode);
});
info = document.getElementById("info");
placeholder = document.getElementById("placeholder");
placeholder.onload = function() {
var item = photos[photoIndex],
days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
months = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ],
taken = new Date(item.taken);
info.textContent = "Photo taken on " +
days[taken.getDay()] + ", " +
months[taken.getMonth()] + " " +
taken.getDate() + " " +
taken.getFullYear();
photo.style.backgroundImage = "url(" + placeholder.src + ")";
schedule();
};
placeholder.onerror = function() {
info.textContent = "Error loading photo. Trying next photo.";
nextPhoto();
/* Trim off everything up to and including the ? (if its there) */
var parts = window.location.href.match(/^.*\?(.*)$/);
if (parts) {
holiday = parts[1];
} else {
holiday = "memorial day";
}
window.fetch("api/v1/photos/holiday/memorial%20day", function(error, xhr) {
if (error && xhr.status != 404) {
console.log("Unable to fetch holiday: " + error);
return;
}
var results;
try {
results = JSON.parse(xhr.responseText);
} catch (___) {
info.textContent = "Unable to parse results"
return;
}
if (results.items.length) {
photos = shuffle(results.items);
photoIndex = -1;
nextPhoto();
} else {
info.textContent = "No photos found.";
}
window.fetch("api/v1/photos/holiday/" + holiday.replace(/ +/g, "%20"))
.then(res => res.json()).then(function(data) {
if (data.items.length) {
info.textContent = photos.length + " photos found. Shuffling.";
photos = shuffle(data.items);
photoIndex = -1;
nextPhoto();
} else {
info.textContent = "No photos found for " + holiday + ".";
}
}).catch(function(error) {
console.error(error);
info.textContent = "Unable to fetch holiday :(";
});
});
</script>

View File

@ -12,6 +12,9 @@
},
"author": "James Ketrenos",
"license": "Apache-2.0",
"unused": {
"face-recognition": "^0.9.4"
},
"dependencies": {
"bluebird": "^3.5.3",
"body-parser": "^1.18.3",
@ -21,7 +24,6 @@
"exif-reader": "github:paras20xx/exif-reader",
"express": "^4.16.4",
"express-session": "^1.15.6",
"face-recognition": "^0.9.4",
"handlebars": "^4.0.12",
"ldapauth-fork": "^4.0.2",
"ldapjs": "^1.0.2",

View File

@ -1,2 +1,2 @@
#!/bin/bash
sqlite3 photos.db
sqlite3 db/photos.db