/*
* Copyright (C) 2014 Glencoe Software, Inc. All rights reserved.
*
* 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 omero.cmd.fs;
import static omero.rtypes.rlong;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ome.api.IQuery;
import ome.conditions.RootException;
import ome.io.nio.PixelsService;
import ome.model.IObject;
import ome.model.core.Image;
import ome.model.core.Pixels;
import ome.model.display.Thumbnail;
import ome.model.fs.Fileset;
import ome.parameters.Parameters;
import ome.security.ACLVoter;
import omero.cmd.ERR;
import omero.cmd.HandleI.Cancel;
import omero.cmd.Helper;
import omero.cmd.IRequest;
import omero.cmd.ManageImageBinaries;
import omero.cmd.ManageImageBinariesResponse;
import omero.cmd.Response;
/**
* Workflow for converting attached original images into OMERO5+ filesets. Input
* is an Image ID and which of the various workflow steps ("deletePixels", etc)
* will be performed. In any case, the image will be loaded and various metrics
* of the used space stored in the {@link ManageImageBinariesResponse} instance.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 5.0.2
*/
public class ManageImageBinariesI extends ManageImageBinaries implements
IRequest {
static class PixelFiles {
final File pixels;
final File backup;
final File pyramid;
PixelFiles(String path) {
pixels = new File(path);
backup = new File(path + "_bak");
pyramid = new File(path + "_pyramid");
}
public void update(ManageImageBinariesResponse rsp) {
if (pixels.exists()) {
rsp.pixelsPresent = true;
rsp.pixelSize = pixels.length();
} else {
rsp.pixelsPresent = false;
if (backup.exists()) {
rsp.pixelSize = backup.length();
}
}
rsp.pyramidPresent = false;
if (pyramid.exists()) {
rsp.pyramidPresent = true;
rsp.pyramidSize = pyramid.length();
}
}
}
private static final long serialVersionUID = -1L;
private final ManageImageBinariesResponse rsp = new ManageImageBinariesResponse();
private final PixelsService pixelsService;
private final ACLVoter voter;
private Helper helper;
private PixelFiles files;
private List<File> thumbnailFiles = new ArrayList<File>();
public ManageImageBinariesI(PixelsService pixelsService,
ACLVoter voter) {
this.pixelsService = pixelsService;
this.voter = voter;
}
//
// CMD API
//
public Map<String, String> getCallContext() {
Map<String, String> all = new HashMap<String, String>();
all.put("omero.group", "-1");
return all;
}
public void init(Helper helper) {
this.helper = helper;
this.helper.setSteps(6);
}
public Object step(int step) {
helper.assertStep(step);
switch (step) {
case 0: findImage(); break;
case 1: findAttached(); break;
case 2: findBinary(); break;
case 3: findFileset(); break;
case 4: togglePixels(); break;
case 5: deletePyramid(); break;
default:
throw helper.cancel(new ERR(), null, "unknown-step", "step" , ""+step);
}
return null;
}
@Override
public void finish() throws Cancel {
// no-op
}
public void buildResponse(int step, Object object) {
helper.assertResponse(step);
if (helper.isLast(step)) {
helper.setResponseIfNull(rsp);
}
}
public Response getResponse() {
return helper.getResponse();
}
//
// STEP METHODS
//
/**
* Simply load the image for this instance, calling cancel if it cannot
* loadable.
*/
private void findImage() {
try {
IQuery query = helper.getServiceFactory().getQueryService();
// Load as a test.
query.get(Image.class, imageId);
} catch (RootException re) {
throw helper.cancel(new ERR(), re, "no-image", "image-id", ""
+ imageId);
}
}
private void findAttached() {
IQuery query = helper.getServiceFactory().getQueryService();
List<IObject> rv = query.findAllByQuery(
"select o from Image i join i.pixels p " +
"join p.pixelsFileMaps m join m.parent o " +
"where i.id = :id", new Parameters().addId(imageId));
rsp.archivedFiles = new ArrayList<Long>();
for (IObject obj : rv) {
long id = obj.getId();
rsp.archivedFiles.add(id);
File f = new File(pixelsService.getFilesPath(id));
if (f.exists()) {
rsp.archivedSize += f.length();
}
}
}
/**
* Use {@link PixelsService} to find pre-FS binary files under
* "/OMERO/Files", "/OMERO/Pixels", and "/OMERO/Thumbnails",
* and store their size in the response.
*/
private void findBinary() {
IQuery query = helper.getServiceFactory().getQueryService();
Pixels pixels = query.get(Image.class, imageId).getPrimaryPixels();
List<Thumbnail> thumbs = query.findAllByQuery(
"select tb from Thumbnail tb where " +
"tb.pixels.id = :id", new Parameters().addId(pixels.getId()));
String path = pixelsService.getPixelsPath(pixels.getId());
files = new PixelFiles(path);
files.update(rsp);
for (Thumbnail tb: thumbs) {
path = pixelsService.getThumbnailPath(tb.getId());
File thumbnailFile = new File(path);
thumbnailFiles.add(thumbnailFile);
rsp.thumbnailSize += thumbnailFile.length();
}
}
/**
* Load the {@link Fileset} for the imageId if it exists.
*/
private void findFileset() {
try {
IQuery query = helper.getServiceFactory().getQueryService();
Fileset fs = query.findByQuery(
"select fs from Image i join i.fileset fs "
+ "where i.id = :id",
new Parameters().addId(imageId));
if (fs != null) {
rsp.filesetId = rlong(fs.getId());
}
} catch (RootException re) {
throw helper.cancel(new ERR(), re, "fileset-load-err", "image-id",
"" + imageId);
}
}
private void togglePixels() {
if (togglePixels) {
requireFileset("pixels");
processFile("pixels-move", files.pixels, files.backup);
files.update(rsp);
}
}
private void deletePyramid() {
if (deletePyramid) {
requireFileset("pyramid");
processFile("pyramid", files.pyramid, null);
files.update(rsp);
}
}
private void requireFileset(String which) {
if (rsp.filesetId == null) {
throw helper.cancel(new ERR(), null, which + "-requires-fileset");
}
}
private void processFile(String which, File file, File dest) {
if (!file.exists()) {
return; // Nothing to do
}
IQuery query = helper.getServiceFactory().getQueryService();
Image image = query.get(Image.class, imageId);
if (!voter.allowDelete(image, image.getDetails())) {
throw helper.cancel(new ERR(), null, which + "-delete-disallowed");
}
if (dest != null) {
if (!file.renameTo(dest)) {
throw helper.cancel(new ERR(), null, which + "-delete-false");
}
} else {
if (!file.delete()) {
// TODO: should we schedule for deleteOnExit here?
throw helper.cancel(new ERR(), null, which + "-delete-false");
}
}
}
}