Skip to content

Generate preview for images #567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ env:
before_script:
- if [ "$SPRING_PROFILES_ACTIVE" = 'travis' ]; then
mysql -u travis -e 'CREATE DATABASE mystamps CHARACTER SET utf8;';
mkdir -p /tmp/uploads;
mkdir -p /tmp/uploads /tmp/preview;
if [ "$TRAVIS_BRANCH" = 'prod' ]; then
pip install --user ansible==2.1.1.0;
fi;
Expand Down
1 change: 1 addition & 0 deletions NEWS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- (functionality) add possibility for adding series sales
- (functionality) add interface for viewing series sales (contributed by Sergey Chechenev)
- (functionality) name of category/country in Russian now are optional fields
- (functionality) preview now is generated after uploading an image

0.3
- (functionality) implemented possibility to user to add series to his collection
Expand Down
6 changes: 3 additions & 3 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ ENV SPRING_PROFILES_ACTIVE=test
# See also: https://docs.docker.com/engine/reference/builder/#run
RUN mkdir /data \
&& useradd mystamps --home-dir /data/mystamps --create-home --comment 'MyStamps' \
&& mkdir /data/uploads /data/heap-dumps \
&& chown mystamps:mystamps /data/uploads /data/heap-dumps
&& mkdir /data/uploads /data/preview /data/heap-dumps \
&& chown mystamps:mystamps /data/uploads /data/preview /data/heap-dumps

# Creates mount points and marks them as holding externally mounted volumes from native host.
# See also: https://docs.docker.com/engine/reference/builder/#volume
VOLUME /data/uploads /data/heap-dumps
VOLUME /data/uploads /data/preview /data/heap-dumps

# Sets the user name to use when running the image and for any subsequent RUN, CMD and ENTRYPOINT instructions.
# See also: https://docs.docker.com/engine/reference/builder/#user
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<version>${javax.validation.version}</version>
</dependency>

<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>${thumbnailator.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand Down Expand Up @@ -521,6 +527,7 @@
<subethasmtp.version>3.1.7</subethasmtp.version>
<surefire.plugin.version>2.19.1</surefire.plugin.version>
<testng.version>6.8.8</testng.version>
<thumbnailator.version>0.4.8</thumbnailator.version>

<!-- Redefine default value from spring-boot-dependencies (https://github.com/spring-projects/spring-boot/blob/v1.4.6.RELEASE/spring-boot-dependencies/pom.xml) -->
<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/ru/mystamps/web/Url.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public final class Url {

public static final String INFO_COLLECTION_PAGE = "/collection/{slug}";

public static final String GET_IMAGE_PAGE = "/image/{id}";
public static final String GET_IMAGE_PAGE = "/image/{id}";
public static final String GET_IMAGE_PREVIEW_PAGE = "/image/preview/{id}";

public static final String FORBIDDEN_PAGE = "/error/403";
public static final String NOT_FOUND_PAGE = "/error/404";
Expand Down Expand Up @@ -145,6 +146,7 @@ public static Map<String, String> asMap(boolean serveContentFromSingleHost) {
map.put("SELECTIZE_CSS", SELECTIZE_CSS);
map.put("SELECTIZE_JS", SELECTIZE_JS);
map.put("GET_IMAGE_PAGE", GET_IMAGE_PAGE);
map.put("GET_IMAGE_PREVIEW_PAGE", GET_IMAGE_PREVIEW_PAGE);
map.put("FAVICON_ICO", FAVICON_ICO);
map.put("MAIN_CSS", MAIN_CSS);
map.put("CATALOG_UTILS_JS", CATALOG_UTILS_JS);
Expand All @@ -153,6 +155,7 @@ public static Map<String, String> asMap(boolean serveContentFromSingleHost) {
} else {
// Use separate domain for our own resources
map.put("GET_IMAGE_PAGE", STATIC_RESOURCES_URL + GET_IMAGE_PAGE);
map.put("GET_IMAGE_PREVIEW_PAGE", STATIC_RESOURCES_URL + GET_IMAGE_PREVIEW_PAGE);
map.put("FAVICON_ICO", STATIC_RESOURCES_URL + FAVICON_ICO);
map.put("MAIN_CSS", STATIC_RESOURCES_URL + MAIN_CSS);
map.put("CATALOG_UTILS_JS", STATIC_RESOURCES_URL + CATALOG_UTILS_JS);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/ru/mystamps/web/config/ServicesConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public CronService getCronService() {
public ImageService getImageService() {
return new ImageServiceImpl(
strategiesConfig.getImagePersistenceStrategy(),
new TimedImagePreviewStrategy(new ThumbnailatorImagePreviewStrategy()),
daoConfig.getImageDao()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class FsStrategiesConfig implements StrategiesConfig {
@Override
public ImagePersistenceStrategy getImagePersistenceStrategy() {
return new FilesystemImagePersistenceStrategy(
env.getRequiredProperty("app.upload.dir")
env.getRequiredProperty("app.upload.dir"),
env.getRequiredProperty("app.preview.dir")
);
}

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/ru/mystamps/web/controller/ImageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,27 @@ public void getImage(@PathVariable("id") Integer imageId, HttpServletResponse re
response.getOutputStream().write(image.getData());
}

@GetMapping(Url.GET_IMAGE_PREVIEW_PAGE)
public void getImagePreview(@PathVariable("id") Integer imageId, HttpServletResponse response)
throws IOException {

if (imageId == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}

ImageDto image = imageService.getOrCreatePreview(imageId);
if (image == null) {
// return original image when error has occurred
getImage(imageId, response);
return;
}

response.setContentType("image/" + image.getType().toLowerCase(Locale.ENGLISH));
response.setContentLength(image.getData().length);

response.getOutputStream().write(image.getData());
}

}

2 changes: 1 addition & 1 deletion src/main/java/ru/mystamps/web/dao/ImageDataDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
import ru.mystamps.web.dao.dto.DbImageDto;

public interface ImageDataDao {
DbImageDto findByImageId(Integer imageId);
DbImageDto findByImageId(Integer imageId, boolean preview);
Integer add(AddImageDataDbDto imageData);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
public class AddImageDataDbDto {
private Integer imageId;
private byte[] content;
private boolean preview;
}
10 changes: 7 additions & 3 deletions src/main/java/ru/mystamps/web/dao/impl/JdbcImageDataDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package ru.mystamps.web.dao.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -48,11 +47,15 @@ public class JdbcImageDataDao implements ImageDataDao {
private String addImageDataSql;

@Override
public DbImageDto findByImageId(Integer imageId) {
public DbImageDto findByImageId(Integer imageId, boolean preview) {
Map<String, Object> params = new HashMap<>();
params.put("image_id", imageId);
params.put("preview", preview);

try {
return jdbcTemplate.queryForObject(
findByImageIdSql,
Collections.singletonMap("image_id", imageId),
params,
RowMappers::forDbImageDto
);
} catch (EmptyResultDataAccessException ignored) {
Expand All @@ -65,6 +68,7 @@ public Integer add(AddImageDataDbDto imageData) {
Map<String, Object> params = new HashMap<>();
params.put("image_id", imageData.getImageId());
params.put("content", imageData.getContent());
params.put("preview", imageData.isPreview());

KeyHolder holder = new GeneratedKeyHolder();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public void save(MultipartFile file, ImageInfoDto image) {
AddImageDataDbDto imageData = new AddImageDataDbDto();
imageData.setImageId(image.getId());
imageData.setContent(file.getBytes());
imageData.setPreview(false);

Integer id = imageDataDao.add(imageData);
LOG.info("Image #{}: meta data has been saved to #{}", image.getId(), id);
Expand All @@ -63,9 +64,21 @@ public void save(MultipartFile file, ImageInfoDto image) {
}
}

@Override
public void savePreview(byte[] data, ImageInfoDto image) {
AddImageDataDbDto imageData = new AddImageDataDbDto();
imageData.setImageId(image.getId());
imageData.setContent(data);
imageData.setPreview(true);

imageDataDao.add(imageData);

LOG.info("Image #{}: preview has been saved", image.getId());
}

@Override
public ImageDto get(ImageInfoDto image) {
DbImageDto imageDto = imageDataDao.findByImageId(image.getId());
DbImageDto imageDto = imageDataDao.findByImageId(image.getId(), false);
if (imageDto == null) {
LOG.warn("Image #{}: content not found", image.getId());
return null;
Expand All @@ -74,4 +87,15 @@ public ImageDto get(ImageInfoDto image) {
return imageDto;
}

@Override
public ImageDto getPreview(ImageInfoDto image) {
DbImageDto imageDto = imageDataDao.findByImageId(image.getId(), true);
if (imageDto == null) {
LOG.info("Image #{}: preview not found", image.getId());
return null;
}

return imageDto;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ public class FilesystemImagePersistenceStrategy implements ImagePersistenceStrat
LoggerFactory.getLogger(FilesystemImagePersistenceStrategy.class);

private final File storageDir;
private final File previewDir;

public FilesystemImagePersistenceStrategy(String storageDir) {
public FilesystemImagePersistenceStrategy(String storageDir, String previewDir) {
this.storageDir = new File(storageDir);
this.previewDir = new File(previewDir);
}

@PostConstruct
Expand All @@ -54,11 +56,29 @@ public void init() {

} else if (!storageDir.canWrite()) {
LOG.warn(
// TODO(java9): log also user: ProcessHandle.current().info().user()
"Directory '{}' exists but isn't writable for the current user! "
+ "Image uploading won't work.",
storageDir
);
}

LOG.info("Image previews will be saved into {} directory", previewDir);

if (!previewDir.exists()) { // NOPMD: ConfusingTernary (it's ok for me)
LOG.warn(
"Directory '{}' doesn't exist! Image preview generation won't work",
previewDir
);

} else if (!previewDir.canWrite()) {
// TODO(java9): log also user: ProcessHandle.current().info().user()
LOG.warn(
"Directory '{}' exists but isn't writable for the current user! "
+ "Image preview generation won't work",
previewDir
);
}
}

@Override
Expand All @@ -74,9 +94,27 @@ public void save(MultipartFile file, ImageInfoDto image) {
}
}

@Override
public void savePreview(byte[] data, ImageInfoDto image) {
try {
Path dest = generateFilePath(previewDir, image);
writeToFile(data, dest);

LOG.info("Image preview data has been written into file {}", dest);

} catch (IOException ex) {
throw new ImagePersistenceException(ex);
}
}

@Override
public ImageDto get(ImageInfoDto image) {
return get(storageDir, image);
return get(storageDir, image, true);
}

@Override
public ImageDto getPreview(ImageInfoDto image) {
return get(previewDir, image, false);
}

// protected to allow spying
Expand All @@ -93,6 +131,11 @@ protected void writeToFile(MultipartFile file, Path dest) throws IOException {
Files.copy(file.getInputStream(), dest);
}

// protected to allow spying
protected void writeToFile(byte[] data, Path dest) throws IOException {
Files.write(dest, data);
}

// protected to allow spying
protected boolean exists(Path path) {
return Files.exists(path);
Expand All @@ -103,10 +146,16 @@ protected byte[] toByteArray(Path dest) throws IOException {
return Files.readAllBytes(dest);
}

private ImageDto get(File dir, ImageInfoDto image) {
private ImageDto get(File dir, ImageInfoDto image, boolean logWarning) {
Path dest = generateFilePath(dir, image);
if (!exists(dest)) {
LOG.warn("Found image without content: #{} ({} doesn't exist)", image.getId(), dest);
if (logWarning) {
LOG.warn(
"Image #{}: content not found ({} doesn't exist)",
image.getId(),
dest
);
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@

public interface ImagePersistenceStrategy {
void save(MultipartFile file, ImageInfoDto image);
void savePreview(byte[] data, ImageInfoDto image);
ImageDto get(ImageInfoDto image);
ImageDto getPreview(ImageInfoDto image);
}
22 changes: 22 additions & 0 deletions src/main/java/ru/mystamps/web/service/ImagePreviewStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2009-2017 Slava Semushin <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ru.mystamps.web.service;

public interface ImagePreviewStrategy {
byte[] createPreview(byte[] image);
}
1 change: 1 addition & 0 deletions src/main/java/ru/mystamps/web/service/ImageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
public interface ImageService {
Integer save(MultipartFile file);
ImageDto get(Integer imageId);
ImageDto getOrCreatePreview(Integer imageId);
void addToSeries(Integer seriesId, Integer imageId);
List<Integer> findBySeriesId(Integer seriesId);
}
Loading