From df0f2e9140f1b84c24729d86dc17ce650b725fb6 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sun, 21 Jul 2019 22:51:51 +0200 Subject: [PATCH] Refactor scanner into multiple files --- api/src/Scanner.js | 398 --------------------- api/src/index.js | 4 +- api/src/resolvers/scanner.js | 2 +- api/src/scanner/Scanner.js | 114 ++++++ api/src/scanner/processImage.js | 79 ++++ api/src/scanner/scanAlbum.js | 71 ++++ api/src/scanner/scanAll.js | 38 ++ api/src/scanner/scanUser.js | 144 ++++++++ api/src/scanner/utils.js | 33 ++ ui/src/Pages/AlbumPage/AlbumPage.js | 19 +- ui/src/Pages/AllAlbumsPage/AlbumGallery.js | 4 +- ui/src/PhotoGallery.js | 9 +- 12 files changed, 501 insertions(+), 414 deletions(-) delete mode 100644 api/src/Scanner.js create mode 100644 api/src/scanner/Scanner.js create mode 100644 api/src/scanner/processImage.js create mode 100644 api/src/scanner/scanAlbum.js create mode 100644 api/src/scanner/scanAll.js create mode 100644 api/src/scanner/scanUser.js create mode 100644 api/src/scanner/utils.js diff --git a/api/src/Scanner.js b/api/src/Scanner.js deleted file mode 100644 index 58616448..00000000 --- a/api/src/Scanner.js +++ /dev/null @@ -1,398 +0,0 @@ -import fs from 'fs-extra' -import path from 'path' -import { resolve as pathResolve, basename as pathBasename } from 'path' -import { PubSub } from 'apollo-server' -import uuid from 'uuid' -import { exiftool } from 'exiftool-vendored' -import sharp from 'sharp' -import readChunk from 'read-chunk' -import imageType from 'image-type' -import { promisify } from 'util' -import config from './config' - -const imageSize = promisify(require('image-size')) - -export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS' - -const isImage = async path => { - const buffer = await readChunk(path, 0, 12) - const type = imageType(buffer) - - return type != null -} - -export const isRawImage = async path => { - const buffer = await readChunk(path, 0, 12) - const { ext } = imageType(buffer) - - const rawTypes = ['cr2', 'arw', 'crw', 'dng'] - - return rawTypes.includes(ext) -} - -class PhotoScanner { - constructor(driver) { - this.driver = driver - this.isRunning = false - this.pubsub = new PubSub() - - this.scanAll = this.scanAll.bind(this) - this.scanAlbum = this.scanAlbum.bind(this) - this.scanUser = this.scanUser.bind(this) - this.processImage = this.processImage.bind(this) - - this.imagesToProgress = 0 - this.finishedImages = 0 - } - - async scanAll() { - this.isRunning = true - - this.pubsub.publish(EVENT_SCANNER_PROGRESS, { - scannerStatusUpdate: { - progress: 0, - finished: false, - error: false, - errorMessage: '', - }, - }) - - let session = this.driver.session() - - let allUserScans = [] - - session.run('MATCH (u:User) return u').subscribe({ - onNext: record => { - const user = record.toObject().u.properties - - console.log('USER', user) - - if (!user.rootPath) { - console.log(`User ${user.username}, has no root path, skipping`) - return - } - - console.log(`Scanning ${user.username}...`) - allUserScans.push(this.scanUser(user)) - }, - onCompleted: () => { - session.close() - this.isRunning = false - - Promise.all(allUserScans) - .then(() => { - console.log( - `Done scanning ${this.finishedImages} of ${this.imagesToProgress}` - ) - this.pubsub.publish(EVENT_SCANNER_PROGRESS, { - scannerStatusUpdate: { - progress: 100, - finished: true, - error: false, - errorMessage: '', - }, - }) - }) - .catch(error => { - console.log('SYNC ERROR', JSON.stringify(error)) - this.pubsub.publish(EVENT_SCANNER_PROGRESS, { - scannerStatusUpdate: { - progress: 0, - finished: false, - error: true, - errorMessage: error.message, - }, - }) - }) - }, - onError: error => { - console.error(error) - - this.pubsub.publish(EVENT_SCANNER_PROGRESS, { - scannerStatusUpdate: { - progress: 0, - finished: false, - error: true, - errorMessage: error.message, - }, - }) - }, - }) - } - - async scanUser(user) { - console.log('Scanning path', user.rootPath) - - const driver = this.driver - const scanAlbum = this.scanAlbum - - let foundAlbumIds = [] - - async function scanPath(path, parentAlbum) { - const list = fs.readdirSync(path) - - let foundImage = false - let newAlbums = [] - - for (const item of list) { - const itemPath = pathResolve(path, item) - // console.log(`Scanning item ${itemPath}...`) - const stat = fs.statSync(itemPath) - - if (stat.isDirectory()) { - const session = driver.session() - let nextParentAlbum = null - - const findAlbumResult = await session.run( - 'MATCH (a:Album { path: {path} }) RETURN a', - { - path: itemPath, - } - ) - - session.close() - - if (findAlbumResult.records.length != 0) { - const album = findAlbumResult.records[0].toObject().a.properties - console.log('Found existing album', album.title) - - foundAlbumIds.push(album.id) - - nextParentAlbum = album.id - scanAlbum(album) - - continue - } - - const { - foundImage: imagesInDirectory, - newAlbums: childAlbums, - } = await scanPath(itemPath, nextParentAlbum) - - if (imagesInDirectory) { - console.log(`Found new album at ${itemPath}`) - const session = driver.session() - - console.log('Adding album') - const albumId = uuid() - const albumResult = await session.run( - `MATCH (u:User { id: {userId} }) - CREATE (a:Album { id: {id}, title: {title}, path: {path} }) - CREATE (u)-[own:OWNS]->(a) - RETURN a`, - { - id: albumId, - userId: user.id, - title: item, - path: itemPath, - } - ) - - foundAlbumIds.push(albumId) - newAlbums.push(albumId) - const album = albumResult.records[0].toObject().a.properties - - if (parentAlbum) { - console.log('Linking parent album for', album.title) - await session.run( - `MATCH (parent:Album { id: {parentId} }) - MATCH (child:Album { id: {childId} }) - CREATE (parent)-[:SUBALBUM]->(child)`, - { - childId: albumId, - parentId: parentAlbum, - } - ) - } - - console.log(`Linking ${childAlbums.length} child albums`) - for (let childAlbum of childAlbums) { - await session.run( - `MATCH (parent:Album { id: {parentId} }) - MATCH (child:Album { id: {childId} }) - CREATE (parent)-[:SUBALBUM]->(child)`, - { - parentId: albumId, - childId: childAlbum, - } - ) - } - - scanAlbum(album) - - session.close() - } - - continue - } - - if (!foundImage && (await isImage(itemPath))) { - foundImage = true - } - } - - return { foundImage, newAlbums } - } - - await scanPath(user.rootPath) - - console.log('Found album ids', foundAlbumIds) - - const session = this.driver.session() - - const userAlbumsResult = await session.run( - 'MATCH (u:User { id: {userId} })-[:OWNS]->(a:Album)-[:CONTAINS]->(p:Photo) WHERE NOT a.id IN {foundAlbums} DETACH DELETE a, p RETURN a', - { userId: user.id, foundAlbums: foundAlbumIds } - ) - - console.log( - `Deleted ${userAlbumsResult.records.length} albums from ${user.username} that was not found locally` - ) - - session.close() - - console.log('User scan complete') - } - - async scanAlbum(album) { - const { title, path, id } = album - console.log('Scanning album', title) - - const list = fs.readdirSync(path) - - for (const item of list) { - const itemPath = pathResolve(path, item) - - if (await isImage(itemPath)) { - const session = this.driver.session() - - this.imagesToProgress++ - - const photoResult = await session.run( - `MATCH (p:Photo {path: {imgPath} })<--(a:Album {id: {albumId}}) RETURN p`, - { - imgPath: itemPath, - albumId: id, - } - ) - - if (photoResult.records.length != 0) { - console.log(`Photo already exists ${item}`) - - const id = photoResult.records[0].get('p').properties.id - - const thumbnailPath = pathResolve( - config.cachePath, - id, - 'thumbnail.jpg' - ) - - if (!(await fs.exists(thumbnailPath))) { - this.processImage(id) - } else { - this.finishedImages++ - } - } else { - console.log(`Found new image at ${itemPath}`) - const imageId = uuid() - await session.run( - `MATCH (a:Album { id: {albumId} }) - CREATE (p:Photo {id: {id}, path: {path}, title: {title} }) - CREATE (a)-[:CONTAINS]->(p)`, - { - id: imageId, - path: itemPath, - title: item, - albumId: id, - } - ) - - this.processImage(imageId) - } - } - } - } - - async processImage(id) { - const session = this.driver.session() - - const result = await session.run(`MATCH (p:Photo { id: {id} }) RETURN p`, { - id, - }) - - await session.run( - `MATCH (p:Photo { id: {id} })-[rel]->(url:PhotoURL) DELETE url, rel`, - { id } - ) - - console.log('PROCESS IMAGE RESULT', result) - - const photo = result.records[0].get('p').properties - - console.log('Processing photo', photo.path) - - const imagePath = path.resolve(config.cachePath, 'images', id) - - await fs.remove(imagePath) - await fs.mkdirp(imagePath) - - let originalPath = photo.path - - if (await isRawImage(photo.path)) { - console.log('Processing RAW image') - - const extractedPath = path.resolve(imagePath, 'extracted.jpg') - await exiftool.extractPreview(photo.path, extractedPath) - - originalPath = extractedPath - } - - // Resize image - const thumbnailPath = path.resolve(imagePath, 'thumbnail.jpg') - await sharp(originalPath) - .jpeg({ quality: 80 }) - .resize(1440, 1080, { fit: 'inside', withoutEnlargement: true }) - .toFile(thumbnailPath) - - const { width: originalWidth, height: originalHeight } = await imageSize( - originalPath - ) - const { width: thumbnailWidth, height: thumbnailHeight } = await imageSize( - thumbnailPath - ) - - await session.run( - `MATCH (p:Photo { id: {id} }) - CREATE (thumbnail:PhotoURL { url: {thumbnailUrl}, width: {thumbnailWidth}, height: {thumbnailHeight} }) - CREATE (original:PhotoURL { url: {originalUrl}, width: {originalWidth}, height: {originalHeight} }) - CREATE (p)-[:THUMBNAIL_URL]->(thumbnail) - CREATE (p)-[:ORIGINAL_URL]->(original) - `, - { - id, - thumbnailUrl: `/images/${id}/${path.basename(thumbnailPath)}`, - thumbnailWidth, - thumbnailHeight, - originalUrl: `/images/${id}/${path.basename(originalPath)}`, - originalWidth, - originalHeight, - } - ) - - session.close() - - console.log('Processing done') - this.finishedImages++ - - this.pubsub.publish(EVENT_SCANNER_PROGRESS, { - scannerStatusUpdate: { - progress: this.finishedImages / this.imagesToProgress, - finished: false, - error: false, - errorMessage: '', - }, - }) - } -} - -export default PhotoScanner diff --git a/api/src/index.js b/api/src/index.js index fadb10e6..b54e3b11 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -8,7 +8,7 @@ import { v1 as neo4j } from 'neo4j-driver' import { makeAugmentedSchema } from 'neo4j-graphql-js' import dotenv from 'dotenv' import http from 'http' -import PhotoScanner from './Scanner' +import PhotoScanner from './scanner/Scanner' import _ from 'lodash' import config from './config' @@ -124,8 +124,6 @@ server.applyMiddleware({ app, graphPath }) app.use('/images/:id/:image', async function(req, res) { const { id, image } = req.params - console.log('image', image) - let user = null try { diff --git a/api/src/resolvers/scanner.js b/api/src/resolvers/scanner.js index 08adda1c..74d19bfa 100644 --- a/api/src/resolvers/scanner.js +++ b/api/src/resolvers/scanner.js @@ -1,4 +1,4 @@ -import { EVENT_SCANNER_PROGRESS } from '../scanner' +import { EVENT_SCANNER_PROGRESS } from '../scanner/Scanner' const Mutation = { async scanAll(root, args, ctx, info) { diff --git a/api/src/scanner/Scanner.js b/api/src/scanner/Scanner.js new file mode 100644 index 00000000..20460011 --- /dev/null +++ b/api/src/scanner/Scanner.js @@ -0,0 +1,114 @@ +import fs from 'fs-extra' +import path from 'path' +import { resolve as pathResolve, basename as pathBasename } from 'path' +import { PubSub } from 'apollo-server' +import uuid from 'uuid' +import { exiftool } from 'exiftool-vendored' +import sharp from 'sharp' +import { isImage, isRawImage } from './utils' +import { promisify } from 'util' +import config from '../config' +import _scanUser from './scanUser' +import _scanAlbum from './scanAlbum' +import _processImage from './processImage' +import _scanAll from './scanAll' + +export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS' + +class PhotoScanner { + constructor(driver) { + this.driver = driver + this.isRunning = false + this.pubsub = new PubSub() + + this.processImage = this.processImage.bind(this) + this.scanAlbum = this.scanAlbum.bind(this) + this.scanUser = this.scanUser.bind(this) + this.scanAll = this.scanAll.bind(this) + + this.imagesToProgress = 0 + this.finishedImages = 0 + + this.addImageToProgress = () => { + this.imagesToProgress++ + } + + this.addFinishedImage = () => { + this.finishedImages++ + } + } + + async scanUser(user) { + await _scanUser({ driver: this.driver, scanAlbum: this.scanAlbum }, user) + } + + async scanAlbum(album) { + await _scanAlbum( + { + driver: this.driver, + addImageToProgress: this.addImageToProgress, + addFinishedImage: this.addFinishedImage, + processImage: this.processImage, + }, + album + ) + + this.pubsub.publish(EVENT_SCANNER_PROGRESS, { + scannerStatusUpdate: { + progress: this.finishedImages / this.imagesToProgress, + finished: false, + error: false, + errorMessage: '', + }, + }) + } + + async processImage(id) { + await _processImage( + { + driver: this.driver, + addFinishedImage: this.addFinishedImage, + }, + id + ) + } + + async scanAll() { + this.pubsub.publish(EVENT_SCANNER_PROGRESS, { + scannerStatusUpdate: { + progress: 0, + finished: false, + error: false, + errorMessage: '', + }, + }) + + try { + await _scanAll({ driver: this.driver, scanUser: this.scanUser }) + } catch (error) { + this.pubsub.publish(EVENT_SCANNER_PROGRESS, { + scannerStatusUpdate: { + progress: 0, + finished: false, + error: true, + errorMessage: error.message, + }, + }) + throw error + } + + console.log( + `Done scanning ${this.finishedImages} of ${this.imagesToProgress}` + ) + this.pubsub.publish(EVENT_SCANNER_PROGRESS, { + scannerStatusUpdate: { + progress: 100, + finished: true, + error: false, + errorMessage: '', + }, + }) + } +} + +export default PhotoScanner diff --git a/api/src/scanner/processImage.js b/api/src/scanner/processImage.js new file mode 100644 index 00000000..da400e07 --- /dev/null +++ b/api/src/scanner/processImage.js @@ -0,0 +1,79 @@ +import fs from 'fs-extra' +import path from 'path' +import { exiftool } from 'exiftool-vendored' +import sharp from 'sharp' +import { isRawImage, imageSize } from './utils' +import config from '../config' + +export default async function processImage({ driver, addFinishedImage }, id) { + const session = driver.session() + + const result = await session.run(`MATCH (p:Photo { id: {id} }) RETURN p`, { + id, + }) + + await session.run( + `MATCH (p:Photo { id: {id} })-[rel]->(url:PhotoURL) DELETE url, rel`, + { id } + ) + + const photo = result.records[0].get('p').properties + + console.log('Processing photo', photo.path) + + const imagePath = path.resolve(config.cachePath, 'images', id) + + await fs.remove(imagePath) + await fs.mkdirp(imagePath) + + let originalPath = photo.path + + if (await isRawImage(photo.path)) { + // console.log('Processing RAW image') + + const extractedPath = path.resolve(imagePath, 'extracted.jpg') + await exiftool.extractPreview(photo.path, extractedPath) + + originalPath = extractedPath + } + + // Resize image + const thumbnailPath = path.resolve(imagePath, 'thumbnail.jpg') + await sharp(originalPath) + .jpeg({ quality: 80 }) + .resize(1440, 1080, { fit: 'inside', withoutEnlargement: true }) + .toFile(thumbnailPath) + + try { + const { width: originalWidth, height: originalHeight } = await imageSize( + originalPath + ) + const { width: thumbnailWidth, height: thumbnailHeight } = await imageSize( + thumbnailPath + ) + + await session.run( + `MATCH (p:Photo { id: {id} }) + CREATE (thumbnail:PhotoURL { url: {thumbnailUrl}, width: {thumbnailWidth}, height: {thumbnailHeight} }) + CREATE (original:PhotoURL { url: {originalUrl}, width: {originalWidth}, height: {originalHeight} }) + CREATE (p)-[:THUMBNAIL_URL]->(thumbnail) + CREATE (p)-[:ORIGINAL_URL]->(original) + `, + { + id, + thumbnailUrl: `/images/${id}/${path.basename(thumbnailPath)}`, + thumbnailWidth, + thumbnailHeight, + originalUrl: `/images/${id}/${path.basename(originalPath)}`, + originalWidth, + originalHeight, + } + ) + } catch (e) { + console.log('Create photo url failed', e) + } + + session.close() + + addFinishedImage() +} diff --git a/api/src/scanner/scanAlbum.js b/api/src/scanner/scanAlbum.js new file mode 100644 index 00000000..6121e106 --- /dev/null +++ b/api/src/scanner/scanAlbum.js @@ -0,0 +1,71 @@ +import fs from 'fs-extra' +import path from 'path' +import uuid from 'uuid' +import { isImage } from './utils' +import config from '../config' + +export default async function scanAlbum( + { driver, addImageToProgress, addFinishedImage, processImage }, + album +) { + const { title, path: albumPath, id } = album + console.log('Scanning album', title) + + const list = await fs.readdir(albumPath) + let processingImagePromises = [] + + for (const item of list) { + const itemPath = path.resolve(albumPath, item) + + if (await isImage(itemPath)) { + const session = driver.session() + + addImageToProgress() + + const photoResult = await session.run( + `MATCH (p:Photo {path: {imgPath} })<--(a:Album {id: {albumId}}) RETURN p`, + { + imgPath: itemPath, + albumId: id, + } + ) + + if (photoResult.records.length != 0) { + // console.log(`Photo already exists ${item}`) + + const id = photoResult.records[0].get('p').properties.id + + const thumbnailPath = path.resolve( + config.cachePath, + id, + 'thumbnail.jpg' + ) + + if (!(await fs.exists(thumbnailPath))) { + processingImagePromises.push(processImage(id)) + } else { + addFinishedImage() + } + } else { + console.log(`Found new image at ${itemPath}`) + const imageId = uuid() + await session.run( + `MATCH (a:Album { id: {albumId} }) + CREATE (p:Photo {id: {id}, path: {path}, title: {title} }) + CREATE (a)-[:CONTAINS]->(p)`, + { + id: imageId, + path: itemPath, + title: item, + albumId: id, + } + ) + + processingImagePromises.push(processImage(imageId)) + } + } + } + + await Promise.all(processingImagePromises) + console.log('Done processing album', album.title) +} diff --git a/api/src/scanner/scanAll.js b/api/src/scanner/scanAll.js new file mode 100644 index 00000000..e9ea2a27 --- /dev/null +++ b/api/src/scanner/scanAll.js @@ -0,0 +1,38 @@ +export default function scanAll({ driver, scanUser }) { + return new Promise((resolve, reject) => { + let session = driver.session() + + let allUserScans = [] + + session.run('MATCH (u:User) return u').subscribe({ + onNext: record => { + const user = record.toObject().u.properties + + if (!user.rootPath) { + console.log(`User ${user.username}, has no root path, skipping`) + return + } + + allUserScans.push( + scanUser(user).catch(reason => { + console.log( + `User scan exception for user ${user.username} ${reason}` + ) + reject(reason) + }) + ) + }, + onCompleted: () => { + session.close() + + Promise.all(allUserScans).then(() => { + resolve() + }) + }, + onError: error => { + session.close() + reject(error) + }, + }) + }) +} diff --git a/api/src/scanner/scanUser.js b/api/src/scanner/scanUser.js new file mode 100644 index 00000000..150b97ad --- /dev/null +++ b/api/src/scanner/scanUser.js @@ -0,0 +1,144 @@ +import fs from 'fs-extra' +import { resolve as pathResolve } from 'path' +import uuid from 'uuid' +import { isImage } from './utils' + +export default async function scanUser({ driver, scanAlbum }, user) { + console.log('Scanning user', user.username, 'at', user.path) + + let foundAlbumIds = [] + let albumScanPromises = [] + + async function scanPath(path, parentAlbum) { + console.log('SCAN PATH', path) + const list = fs.readdirSync(path) + + let foundImage = false + let newAlbums = [] + + for (const item of list) { + const itemPath = pathResolve(path, item) + // console.log(`Scanning item ${itemPath}...`) + let stat = null + + try { + stat = await fs.stat(itemPath) + } catch { + console.log('ERROR reading file stat for item:', itemPath) + } + + if (stat && stat.isDirectory()) { + const session = driver.session() + let nextParentAlbum = null + + const findAlbumResult = await session.run( + 'MATCH (a:Album { path: {path} }) RETURN a', + { + path: itemPath, + } + ) + + session.close() + + const { + foundImage: imagesInDirectory, + newAlbums: childAlbums, + } = await scanPath(itemPath, nextParentAlbum) + + if (findAlbumResult.records.length > 0) { + const album = findAlbumResult.records[0].toObject().a.properties + console.log('Found existing album', album.title) + + nextParentAlbum = album.id + foundAlbumIds.push(album.id) + albumScanPromises.push(scanAlbum(album)) + + continue + } + + if (imagesInDirectory) { + console.log(`Found new album at ${itemPath}`) + const session = driver.session() + + console.log('Adding album') + const albumId = uuid() + const albumResult = await session.run( + `MATCH (u:User { id: {userId} }) + CREATE (a:Album { id: {id}, title: {title}, path: {path} }) + CREATE (u)-[own:OWNS]->(a) + RETURN a`, + { + id: albumId, + userId: user.id, + title: item, + path: itemPath, + } + ) + + newAlbums.push(albumId) + const album = albumResult.records[0].toObject().a.properties + + if (parentAlbum) { + console.log('Linking parent album for', album.title) + await session.run( + `MATCH (parent:Album { id: {parentId} }) + MATCH (child:Album { id: {childId} }) + MERGE (parent)-[:SUBALBUM]->(child)`, + { + childId: albumId, + parentId: parentAlbum, + } + ) + } + + console.log(`Linking ${childAlbums.length} child albums`) + for (let childAlbum of childAlbums) { + await session.run( + `MATCH (parent:Album { id: {parentId} }) + MATCH (child:Album { id: {childId} }) + CREATE (parent)-[:SUBALBUM]->(child)`, + { + parentId: albumId, + childId: childAlbum, + } + ) + } + + foundAlbumIds.push(album.id) + albumScanPromises.push(scanAlbum(album)) + + session.close() + } + + continue + } + + if (!foundImage && (await isImage(itemPath))) { + foundImage = true + } + } + + return { foundImage, newAlbums } + } + + await scanPath(user.rootPath) + + console.log('Found album ids', foundAlbumIds) + + const session = driver.session() + + const userAlbumsResult = await session.run( + 'MATCH (u:User { id: {userId} })-[:OWNS]->(a:Album)-[:CONTAINS]->(p:Photo) WHERE NOT a.id IN {foundAlbums} DETACH DELETE a, p RETURN a', + { userId: user.id, foundAlbums: foundAlbumIds } + ) + + console.log( + `Deleted ${userAlbumsResult.records.length} albums from ${user.username} that was not found locally` + ) + + session.close() + + await Promise.all(albumScanPromises) + + console.log('User scan complete') +} diff --git a/api/src/scanner/utils.js b/api/src/scanner/utils.js new file mode 100644 index 00000000..9bada1db --- /dev/null +++ b/api/src/scanner/utils.js @@ -0,0 +1,33 @@ +import fs from 'fs-extra' +import readChunk from 'read-chunk' +import imageType from 'image-type' +import { promisify } from 'util' + +export const isImage = async path => { + if ((await fs.stat(path)).isDirectory()) { + return false + } + + try { + const buffer = await readChunk(path, 0, 12) + const type = imageType(buffer) + return type != null + } catch (e) { + throw new Error(`isImage error at ${path}: ${JSON.stringify(e)}`) + } +} + +export const isRawImage = async path => { + try { + const buffer = await readChunk(path, 0, 12) + const { ext } = imageType(buffer) + + const rawTypes = ['cr2', 'arw', 'crw', 'dng'] + + return rawTypes.includes(ext) + } catch (e) { + throw new Error(`isRawImage error at ${path}: ${JSON.stringify(e)}`) + } +} + +export const imageSize = promisify(require('image-size')) diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js index 47242726..385179fb 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.js +++ b/ui/src/Pages/AlbumPage/AlbumPage.js @@ -4,6 +4,7 @@ import { Query } from 'react-apollo' import Layout from '../../Layout' import AlbumSidebar from './AlbumSidebar' import PhotoGallery from '../../PhotoGallery' +import AlbumGallery from '../AllAlbumsPage/AlbumGallery' const albumQuery = gql` query albumQuery($id: ID) { @@ -85,15 +86,31 @@ class AlbumPage extends Component { {({ loading, error, data }) => { if (error) return
Error
+ let subAlbumElement = null + if (data.album) { this.photoAmount = data.album.photos.length + + if (data.album.subAlbums.length > 0) { + subAlbumElement = ( + + ) + } } return (
+

{data.album && data.album.title}

+ {subAlbumElement} + {data.album && data.album.subAlbums.length > 0 && ( +

Images

+ )} { diff --git a/ui/src/Pages/AllAlbumsPage/AlbumGallery.js b/ui/src/Pages/AllAlbumsPage/AlbumGallery.js index b10b081e..b1b1439f 100644 --- a/ui/src/Pages/AllAlbumsPage/AlbumGallery.js +++ b/ui/src/Pages/AllAlbumsPage/AlbumGallery.js @@ -4,10 +4,8 @@ import { Loader } from 'semantic-ui-react' import { AlbumBox } from './AlbumBox' const Container = styled.div` - margin: -10px; - margin-top: 20px; + margin: 20px -10px; position: relative; - min-height: 500px; ` const AlbumGallery = ({ loading, error, albums }) => { diff --git a/ui/src/PhotoGallery.js b/ui/src/PhotoGallery.js index 4821989d..9aa81b73 100644 --- a/ui/src/PhotoGallery.js +++ b/ui/src/PhotoGallery.js @@ -60,13 +60,7 @@ const PhotoFiller = styled.div` flex-grow: 999999; ` -const PhotoGallery = ({ - activeIndex = -1, - photos, - loading, - title, - onSelectImage, -}) => { +const PhotoGallery = ({ activeIndex = -1, photos, loading, onSelectImage }) => { let photoElements = null if (photos) { photoElements = photos.map((photo, index) => { @@ -96,7 +90,6 @@ const PhotoGallery = ({ return (
-

{title}

Loading images {photoElements}