/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.resources.util;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import net.java.sip.communicator.service.resources.*;
/**
* Class for building of skin bundles from zip files.
* @author Adam Netocny
*/
public class SkinJarBuilder
{
/**
* Creates bundle from zip file.
* @param srv <tt>ResourcePack</tt> containing class files and manifest
* for the SkinResourcePack.
* @param zip Zip file with skin contents.
* @return Jar <tt>File</tt>.
* @throws Exception When something goes wrong.
*/
public static File createBundleFromZip(File zip, ResourcePack srv)
throws Exception
{
File tmpDir = unzipIntoTmp(zip);
File tmpDir2 = findBase(tmpDir);
if (tmpDir2 == null)
{
tmpDir2 = tmpDir;
}
if (!test(tmpDir2))
{
deleteDir(tmpDir);
throw new Exception(
"Zip file doesn't contain all necessary files and folders.");
}
cpTmp(tmpDir2, srv);
File jar = insertIntoZip(tmpDir2);
deleteDir(tmpDir);
return jar;
}
/**
* Creates a copy of skinresources.jar in temp folder.
*
* @param unzippedBase Base dir where files should appear.
* @param srv <tt>ResourcePack</tt> containing class files and manifest
* for the SkinResourcePack.
* @throws IOException Is thrown if the jar cannot be located or if a file
* operation goes wrong.
*/
private static void cpTmp(File unzippedBase, ResourcePack srv)
throws IOException
{
InputStream in = srv.getClass().getClassLoader()
.getResourceAsStream(
"resources/skinresourcepack/SkinResourcePack.class");
File dest = new File(unzippedBase, "net" + File.separatorChar + "java"
+ File.separatorChar + "sip" + File.separatorChar
+ "communicator" + File.separatorChar + "plugin"
+ File.separatorChar + "skinresourcepack");
if(!dest.mkdirs())
{
throw new IOException("Unable to build resource pack.");
}
OutputStream out = new FileOutputStream(
new File(dest, "SkinResourcePack.class"));
copy(in, out);
in = srv.getClass().getClassLoader()
.getResourceAsStream(
"resources/skinresourcepack/skinresourcepack.manifest.mf");
dest = new File(unzippedBase, "META-INF");
if(!dest.mkdirs()) {
throw new IOException("Unable to build resource pack.");
}
out = new FileOutputStream(new File(dest, "MANIFEST.MF"));
copy(in, out);
}
/**
* Simple file copy operation.
* @param in <tt>InputStream</tt> for the source.
* @param out <tt>OutputStream</tt> for the destination file.
* @throws IOException Is thrown if the jar cannot be located or if a file
* operation goes wrong.
*/
private static void copy(InputStream in, OutputStream out) throws IOException
{
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();
}
/**
* Unzips a specified <tt>File</tt> to temp folder.
*
* @param zip ZIP <tt>File</tt> to be unzipped.
* @return temporary directory with the content of the ZIP file.
* @throws IOException Is thrown if a file operation goes wrong.
*/
private static File unzipIntoTmp(File zip)
throws IOException
{
File dest = File.createTempFile("zip", null);
if (!dest.delete())
throw new IOException("Cannot unzip given zip file");
if (!dest.mkdirs())
throw new IOException("Cannot unzip given zip file");
ZipFile archive = new ZipFile(zip);
try
{
Enumeration<? extends ZipEntry> e = archive.entries();
if (e.hasMoreElements())
{
byte[] buffer = new byte[8192];
while (e.hasMoreElements())
{
ZipEntry entry = e.nextElement();
File file = new File(dest, entry.getName());
if (entry.isDirectory() && !file.exists())
{
file.mkdirs();
}
else
{
File parentFile = file.getParentFile();
if (!parentFile.exists())
parentFile.mkdirs();
InputStream in = archive.getInputStream(entry);
try
{
BufferedOutputStream out
= new BufferedOutputStream(
new FileOutputStream(file));
try
{
int read;
while (-1 != (read = in.read(buffer)))
out.write(buffer, 0, read);
}
finally
{
out.close();
}
}
finally
{
in.close();
}
}
}
}
}
finally
{
archive.close();
}
return dest;
}
/**
* Inserts files into ZIP file.
*
* @param tmpDir Folder which contains the data.
* @return <tt>File</tt> containing reference of the jar file.
* @throws IOException Is thrown if a file operation goes wrong.
*/
private static File insertIntoZip(File tmpDir)
throws IOException
{
File jar = File.createTempFile("skinresourcepack", ".jar");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(jar));
zipDir(tmpDir.getAbsolutePath(), out);
out.close();
return jar;
}
/**
* Zips the content of a folder.
* @param dir2zip Path to the directory with the data to be stored.
* @param zos Opened <tt>ZipOutputStream</tt> in which will be information
* stored.
* @throws IOException Is thrown if a file operation goes wrong.
*/
private static void zipDir(String dir2zip, ZipOutputStream zos)
throws IOException
{
File directory = new File(dir2zip);
zip(directory, directory, zos);
}
/**
* Zips a file.
* @param directory Path to the dir with the data to be stored.
* @param base Base path for cutting paths into zip entries.
* @param zos Opened <tt>ZipOutputStream</tt> in which will be information
* stored.
* @throws IOException Is thrown if a file operation goes wrong.
*/
private static final void zip(File directory, File base, ZipOutputStream zos)
throws IOException
{
File[] files = directory.listFiles();
byte[] buffer = new byte[8192];
int read = 0;
for (int i = 0, n = files.length; i < n; i++)
{
if (files[i].isDirectory())
{
zip(files[i], base, zos);
}
else
{
FileInputStream in = new FileInputStream(files[i]);
ZipEntry entry = new ZipEntry(files[i].getPath().substring(
base.getPath().length() + 1));
zos.putNextEntry(entry);
while (-1 != (read = in.read(buffer))) {
zos.write(buffer, 0, read);
}
in.close();
}
}
}
/**
* Deletes a directory with all its sub-directories.
*
* @param tmp the directory to be deleted
*/
private static void deleteDir(File tmp)
{
if (tmp.exists())
{
File[] files = tmp.listFiles();
for (int i = 0; i < files.length; i++)
{
if (files[i].isDirectory())
{
deleteDir(files[i]);
}
else
{
files[i].delete();
}
}
tmp.delete();
}
}
/**
* Tests if the content of a folder has the same structure as the skin
* content.
*
* @param tmpDir Directory to be tested.
* @return <tt>true</tt> - if the directory contains valid skin, else
* <tt>false</tt>.
*/
private static boolean test(File tmpDir)
{
boolean colors = false;
boolean images = false;
boolean styles = false;
File[] list = tmpDir.listFiles();
if (list == null)
{
return false;
}
for (File f : list)
{
if (f.getName().equals("info.properties"))
{
if (!f.isFile())
{
return false;
}
}
else if (f.getName().equals("colors"))
{
if (f.isFile())
{
return false;
}
File[] ff = f.listFiles();
if (ff == null)
{
return false;
}
for (File x : ff)
{
if (x.getName().equals("colors.properties"))
{
colors = true;
}
}
}
else if (f.getName().equals("images"))
{
if (f.isFile())
{
return false;
}
File[] ff = f.listFiles();
if (ff == null)
{
return false;
}
for (File x : ff)
{
if (x.getName().equals("images.properties"))
{
images = true;
}
}
}
else if (f.getName().equals("styles"))
{
if (f.isFile())
{
return false;
}
File[] ff = f.listFiles();
if (ff == null)
{
return false;
}
for (File x : ff)
{
if (x.getName().equals("styles.properties"))
{
styles = true;
}
}
}
}
return styles || (colors || images);
}
/**
* Moves to top level directory for unziped files. (e.g.
* /dir/info.propreties will be changed to /info.properties.)
* @param tmpDir Directory in which is the skin unzipped.
* @return the top level directory
*/
private static File findBase(File tmpDir)
{
File[] list = tmpDir.listFiles();
if (list == null)
{
return null;
}
boolean test = false;
for (File f : list)
{
if (f.getName().equals("info.properties"))
{
if (f.isFile())
{
test = true;
}
}
}
if (!test)
{
if (list.length != 0)
{
File tmp = null;
for (File f : list)
{
if(f.isDirectory())
{
File tmp2 = findBase(f);
if(tmp2 != null && tmp == null)
{
tmp = tmp2;
}
}
}
return tmp;
}
else
{
return null;
}
}
return tmpDir;
}
}