/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.websocket.command;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.PathHelper;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.graph.Tx;
import org.structr.core.property.PropertyMap;
import org.structr.dynamic.File;
import org.structr.web.common.FileHelper;
import org.structr.web.common.ImageHelper;
import org.structr.web.entity.AbstractFile;
import org.structr.web.entity.FileBase;
import org.structr.web.entity.Folder;
import org.structr.web.entity.Image;
import org.structr.websocket.StructrWebSocket;
import org.structr.websocket.message.MessageBuilder;
import org.structr.websocket.message.WebSocketMessage;
//~--- classes ----------------------------------------------------------------
/**
* Websocket command for un-archiving archive files.
*
*
*/
public class UnarchiveCommand extends AbstractCommand {
private static final Logger logger = LoggerFactory.getLogger(UnarchiveCommand.class.getName());
static {
StructrWebSocket.addCommand(UnarchiveCommand.class);
}
//~--- methods --------------------------------------------------------
@Override
public void processMessage(final WebSocketMessage webSocketData) {
final Set<String> supportedByArchiveStreamFactory = new HashSet<>(Arrays.asList(new String[]{
ArchiveStreamFactory.AR,
ArchiveStreamFactory.ARJ,
ArchiveStreamFactory.CPIO,
ArchiveStreamFactory.DUMP,
ArchiveStreamFactory.JAR,
ArchiveStreamFactory.TAR,
ArchiveStreamFactory.ZIP
}));
final SecurityContext securityContext = getWebSocket().getSecurityContext();
final App app = StructrApp.getInstance(securityContext);
try {
final String id = (String) webSocketData.getId();
final String parentFolderId = (String) webSocketData.getNodeData().get("parentFolderId");
final FileBase file;
try (final Tx tx = app.tx()) {
file = app.get(FileBase.class, id);
if (file == null) {
getWebSocket().send(MessageBuilder.status().code(400).message("File not found: ".concat(id)).build(), true);
return;
}
final String fileExtension = StringUtils.substringAfterLast(file.getName(), ".");
if (!supportedByArchiveStreamFactory.contains(fileExtension)) {
getWebSocket().send(MessageBuilder.status().code(400).message("Unsupported archive format: ".concat(fileExtension)).build(), true);
return;
}
tx.success();
}
// no transaction here since this is a bulk command
unarchive(securityContext, file, parentFolderId);
} catch (Throwable t) {
logger.warn("", t);
String msg = t.toString();
try (final Tx tx = app.tx()) {
// return error message
getWebSocket().send(MessageBuilder.status().code(400).message("Could not unarchive file: ".concat((msg != null) ? msg : "")).build(), true);
getWebSocket().send(MessageBuilder.finished().callback(callback).data("success", false).build(), true);
tx.success();
} catch (FrameworkException ignore) {
}
}
}
@Override
public boolean requiresEnclosingTransaction() {
return false;
}
private void unarchive(final SecurityContext securityContext, final FileBase file, final String parentFolderId) throws ArchiveException, IOException, FrameworkException {
final App app = StructrApp.getInstance(securityContext);
final InputStream is;
Folder existingParentFolder = null;
final String fileName = file.getName();
try (final Tx tx = app.tx()) {
// search for existing parent folder
existingParentFolder = app.get(Folder.class, parentFolderId);
String parentFolderName = null;
String msgString = "Unarchiving file {}";
if (existingParentFolder != null) {
parentFolderName = existingParentFolder.getName();
msgString += " into existing folder {}.";
}
logger.info(msgString, new Object[]{fileName, parentFolderName});
is = file.getInputStream();
tx.success();
if (is == null) {
getWebSocket().send(MessageBuilder.status().code(400).message("Could not get input stream from file ".concat(fileName)).build(), true);
return;
}
tx.success();
}
try (final ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream(new BufferedInputStream(is))) {
ArchiveEntry entry = in.getNextEntry();
int overallCount = 0;
while (entry != null) {
try (final Tx tx = app.tx(true, true, false)) { // don't send notifications for bulk commands
int count = 0;
while (entry != null && count++ < 50) {
final String entryPath = "/" + PathHelper.clean(entry.getName());
logger.info("Entry path: {}", entryPath);
if (entry.isDirectory()) {
final String folderPath = (existingParentFolder != null ? existingParentFolder.getPath() : "") + PathHelper.PATH_SEP + entryPath;
final Folder newFolder = FileHelper.createFolderPath(securityContext, folderPath);
logger.info("Created folder {} with path {}", new Object[]{newFolder, FileHelper.getFolderPath(newFolder)});
} else {
final String filePath = (existingParentFolder != null ? existingParentFolder.getPath() : "") + PathHelper.PATH_SEP + entryPath;
final String name = PathHelper.getName(entryPath);
AbstractFile newFile = ImageHelper.isImageType(name)
? ImageHelper.createImage(securityContext, in, null, Image.class, name, false)
: FileHelper.createFile(securityContext, in, null, File.class, name);
final String folderPath = StringUtils.substringBeforeLast(filePath, PathHelper.PATH_SEP);
final Folder parentFolder = FileHelper.createFolderPath(securityContext, folderPath);
if (parentFolder != null) {
newFile.setProperties(securityContext, new PropertyMap(AbstractFile.parent, parentFolder));
}
// create thumbnails while importing data
// if (newFile instanceof Image) {
// newFile.getProperty(Image.tnMid);
// newFile.getProperty(Image.tnSmall);
// }
logger.info("Created {} file {} with path {}", new Object[]{newFile.getType(), newFile, FileHelper.getFolderPath(newFile)});
}
entry = in.getNextEntry();
overallCount++;
}
logger.info("Committing transaction after {} files.", overallCount);
tx.success();
logger.info("Unarchived {} files.", overallCount);
}
}
}
getWebSocket().send(MessageBuilder.finished().callback(callback).data("success", true).data("filename", fileName).build(), true);
}
//~--- get methods ----------------------------------------------------
@Override
public String getCommand() {
return "UNARCHIVE";
}
}