/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV
This program 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.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.extension.install;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import com.servoy.extension.ExtensionUtils;
import com.servoy.extension.FileBasedExtensionProvider;
import com.servoy.extension.IMessageProvider;
import com.servoy.extension.Message;
import com.servoy.extension.MessageKeeper;
import com.servoy.extension.parser.ExtensionConfiguration;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.SortedList;
import com.servoy.j2db.util.Utils;
/**
* Copies entries from exp file to a Servoy install folder.
*
* @author lvostinar
*
*/
public class CopyZipEntryImporter implements IMessageProvider
{
private final static String BACKUP_FOLDER = ExtensionUtils.EXPFILES_FOLDER + "/.backup"; //$NON-NLS-1$
final static String WEBTEMPLATES_SOURCE_FOLDER = "application_server/webtemplates"; //$NON-NLS-1$
final static String WEBTEMPLATES_DESTINATION_FOLDER = "application_server/server/webapps/ROOT/servoy-webclient/templates"; //$NON-NLS-1$
protected final File expFile;
protected final File installDir;
protected final String extensionID;
protected final String version;
private final File screenshotsFolder;
private final File developerFolder;
private final File docsFolder;
private final File iconFile;
protected final MessageKeeper messages = new MessageKeeper();
public CopyZipEntryImporter(File expFile, File installDir, String extensionID, String version, ExtensionConfiguration expConfig)
{
this.expFile = expFile;
this.installDir = installDir;
this.version = version;
this.extensionID = extensionID;
screenshotsFolder = new File(installDir, "screenshots"); //$NON-NLS-1$
developerFolder = new File(installDir, "developer"); //$NON-NLS-1$
docsFolder = new File(installDir, "application_server/docs/" + extensionID); //$NON-NLS-1$
iconFile = (expConfig.getInfo() != null && expConfig.getInfo().iconPath != null) ? new File(installDir, expConfig.getInfo().iconPath) : null;
}
public void handleFile()
{
if (expFile != null && expFile.exists() && expFile.isFile() && expFile.canRead() &&
expFile.getName().endsWith(FileBasedExtensionProvider.EXTENSION_PACKAGE_FILE_EXTENSION) && installDir != null && installDir.exists() &&
installDir.isDirectory() && installDir.canWrite())
{
ZipFile zipFile = null;
try
{
zipFile = new ZipFile(expFile);
Enumeration< ? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements())
{
ZipEntry entry = entries.nextElement();
if (!entry.isDirectory())
{
String fileName = entry.getName().replace('\\', '/');
fileName = fileName.replace(WEBTEMPLATES_SOURCE_FOLDER, WEBTEMPLATES_DESTINATION_FOLDER);
File outputFile = new File(installDir, fileName);
handleZipEntry(outputFile, zipFile, entry);
}
}
}
catch (IOException ex)
{
String tmp = "Exception while handling entries of expFile: " + expFile; //$NON-NLS-1$
messages.addError(tmp);
Debug.error(tmp, ex);
}
finally
{
if (zipFile != null)
{
try
{
zipFile.close();
}
catch (IOException e)
{
// ignore
}
}
enforceBackUpFolderLimit();
}
handleExpFile();
}
else
{
// shouldn't happen
String tmp = "Invalid install/uninstall file/destination: " + expFile + ", " + installDir; //$NON-NLS-1$//$NON-NLS-2$
messages.addError(tmp);
Debug.error(tmp);
}
}
protected void handleZipEntry(File outputFile, ZipFile zipFile, ZipEntry entry) throws IOException
{
copyFile(outputFile, new BufferedInputStream(zipFile.getInputStream(entry)), false);
}
protected void handleExpFile()
{
String generatedName = extensionID + "_" + version + ".exp"; //$NON-NLS-1$ //$NON-NLS-2$
File expCopy = new File(installDir + File.separator + ExtensionUtils.EXPFILES_FOLDER, generatedName);
InputStream stream = null;
try
{
stream = new BufferedInputStream(new FileInputStream(expFile));
copyFile(expCopy, stream, false);
}
catch (IOException ex)
{
String tmp = "Exception while handling expFile: " + expFile; //$NON-NLS-1$
messages.addError(tmp);
Debug.error(tmp, ex);
}
finally
{
Utils.closeInputStream(stream);
}
}
protected void enforceBackUpFolderLimit()
{
// limit backup folder size to 1 GB; although it's hudge, it's there just not to cause HDD problems because of un-called for backups
final int MAX = 1024 * 1024 * 1024;
File backUpFolder = new File(installDir + File.separator + BACKUP_FOLDER);
if (backUpFolder.exists() && backUpFolder.isDirectory())
{
long size = FileUtils.sizeOfDirectory(backUpFolder);
if (size > MAX)
{
// delete oldest files first
long sizeOverflow = size - MAX;
List<File> sortedByDate = new SortedList<File>(new Comparator<File>()
{
public int compare(File o1, File o2)
{
long result = o1.lastModified() - o2.lastModified();
return (result < 0) ? -1 : (result == 0 ? 0 : 1);
}
});
sortedByDate.addAll(Arrays.asList(backUpFolder.listFiles()));
for (File f : sortedByDate)
{
sizeOverflow -= FileUtils.sizeOf(f);
FileUtils.deleteQuietly(f);
if (f.exists()) sizeOverflow += FileUtils.sizeOf(f);
if (sizeOverflow < 0) break;
}
}
}
}
private void copyFile(File outputFile, InputStream inputStream, boolean skipBackup)
{
try
{
if (skipFile(outputFile, true))
{
return;
}
if (outputFile.exists())
{
if (!skipBackup)
{
messages.addWarning("Destination file is already there; old file will be backed up and overwritten: " + outputFile); //$NON-NLS-1$
backUpReplacedFile(outputFile);
}
}
else
{
if (!outputFile.getParentFile().exists())
{
outputFile.getParentFile().mkdirs();
}
outputFile.createNewFile();
}
BufferedOutputStream out = null;
try
{
out = new BufferedOutputStream(new FileOutputStream(outputFile));
Utils.streamCopy(inputStream, out);
}
finally
{
Utils.closeOutputStream(out);
}
}
catch (Exception ex)
{
String tmp = "Cannot copy file: " + outputFile; //$NON-NLS-1$
messages.addError(tmp);
Debug.error(tmp, ex);
}
finally
{
Utils.closeInputStream(inputStream);
}
}
private void backUpReplacedFile(File sourceFile)
{
File backUpFolder = new File(installDir + File.separator + BACKUP_FOLDER);
boolean ok = true;
if (!backUpFolder.exists())
{
ok = backUpFolder.mkdirs();
}
else if (!backUpFolder.isDirectory())
{
ok = backUpFolder.delete();
if (ok) ok = backUpFolder.mkdirs();
}
if (ok)
{
int i = 0;
File backUpDestFile;
do
{
backUpDestFile = new File(backUpFolder, sourceFile.getName() + ".bk" + i++); //$NON-NLS-1$
}
while (backUpDestFile.exists());
// do actual copy
BufferedOutputStream out = null;
BufferedInputStream in = null;
try
{
out = new BufferedOutputStream(new FileOutputStream(backUpDestFile));
in = new BufferedInputStream(new FileInputStream(sourceFile));
Utils.streamCopy(in, out);
}
catch (IOException e)
{
String tmp = "Cannot back-up replaced file at extension install: " + sourceFile; //$NON-NLS-1$
messages.addError(tmp);
Debug.error(tmp, e);
}
finally
{
Utils.closeInputStream(in);
Utils.closeOutputStream(out);
}
}
else
{
String tmp = "Cannot back-up replaced file at extension install; backup folder not accessible: " + sourceFile; //$NON-NLS-1$
messages.addError(tmp);
Debug.error(tmp);
}
}
protected boolean skipFile(File outputFile, boolean logReasons)
{
if (!ExtensionUtils.isInParentDir(installDir, outputFile))
{
if (logReasons) messages.addWarning("Cannot affect files outside of the install dir; this file will be skipped: " + outputFile); //$NON-NLS-1$
return true;
}
if (outputFile.getName().equals("package.xml")) return true; //$NON-NLS-1$
if (iconFile != null && iconFile.equals(outputFile)) return true;
if (ExtensionUtils.isInParentDir(screenshotsFolder, outputFile)) return true;
if (!developerFolder.exists() && ExtensionUtils.isInParentDir(developerFolder, outputFile))
{
if (logReasons) messages.addWarning("Skipping file because developer folder does not exist: " + outputFile); //$NON-NLS-1$
return true;
}
if (ExtensionUtils.isInParentDir(docsFolder.getParentFile(), outputFile) && !ExtensionUtils.isInParentDir(docsFolder, outputFile))
{
if (logReasons) messages.addWarning("Skipping documentation file (incorrect extension id folder): " + outputFile); //$NON-NLS-1$
return true;
}
return false;
}
public Message[] getMessages()
{
return messages.getMessages();
}
public void clearMessages()
{
messages.clearMessages();
}
}