/* Copyright (C) 2009 Mobile Sorcery AB
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
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 Eclipse Public License v1.0 for
more details.
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.builder.linux;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.mobilesorcery.sdk.builder.linux.deb.BuilderUtil;
/**
* This package unpacks and parses a template package, recursivly
* resolving variables in file names and files. It also parses the
* package meta data which is in JSON,
*
* @author Ali Mosavian
*/
public class PackageParser
{
private Set<String> m_parseSet;
private Map<String, String> m_iconMap;
private String m_binaryPath;
private VariableResolver m_varResolver;
private List<String> m_dependsList;
private List<String> m_requiresList;
private Map<String, String> m_scriptMap;
private Map<String, Integer> m_filemodeMap;
private String m_programFile;
private String m_resourceFile;
/**
* Constructor
*/
public PackageParser ( )
{
m_parseSet = new HashSet<String>( );
m_iconMap = new HashMap<String, String>( );
m_varResolver = new VariableResolver( );
m_dependsList = new LinkedList<String>( );
m_requiresList= new LinkedList<String>( );
m_scriptMap = new HashMap<String, String>( );
m_filemodeMap = new HashMap<String, Integer>( );
// Sets defaults
setAppCategories( "" );
}
/**
* Sets the application name variable
*
* @param v Name
*/
public void setAppName ( String v )
{
addVariable( "appname", v );
setSummary( v );
}
/**
* Set the applications summary.
* Note: Keep it short and no newlines
*
* @param v Summary
*/
public void setSummary ( String v )
{
addVariable( "description", v );
}
/**
* Sets application category, see gnome/kde/clutter manual
*
* @param v Category
*/
public void setAppCategories ( String v )
{
addVariable( "categories", v );
}
/**
* Returns the directory where the program and resource
* file needs to be inserted into
*
* @return Program dir or null
*/
public String getProgramDir ( )
{
return m_binaryPath;
}
/**
* Returns png icon directory
*
* @param s Icon size, usually 16 ( 16x16 ), 32, 64, 128, 256
* @return Null if no such directory
*/
public String getPNGIconDir ( int s )
{
if ( m_iconMap.containsKey( "png"+s ) == false )
return null;
return m_iconMap.get( "png"+s );
}
/**
* Returns svg icon directory
* Note: If SVG is used, png usually isn't needed.
*
* @return Null if no such directory
*/
public String getSVGIconDir ( )
{
if ( m_iconMap.containsKey( "svg" ) == false )
return null;
return m_iconMap.get( "svg" );
}
/**
* Sets a variable value pair, this is used to resolve
* unknown variables during template package processing.
*
* @param s Variable name
* @param v Variable value
*/
public void addVariable ( String s,
String v )
{
m_varResolver.addVariable( s.toLowerCase( ), v );
}
/**
* Returns DEB package dependency list
*
* @return List of Strings
*/
public List<String> getDependsList ( )
{
return m_dependsList;
}
/**
* Returns RPM package dependency list
*
* @return List of Strings
*/
public List<String> getRequiresList ( )
{
return m_requiresList;
}
/**
* Returns unix file mode for every path in the template.
*
* @param n File/path name
* @return file mode
*/
public int getFileMode ( String n )
{
if ( n.charAt( 0 ) == '/' )
n = n.substring( 1 );
if ( m_filemodeMap.containsKey( n ) == false )
return 0x1a4;
return m_filemodeMap.get( n );
}
/**
* Returns the path to the program file
*
*/
public String getProgramFilePath ( )
{
return m_programFile;
}
/**
* Returns the path to the resource file
*
*/
public String getResourceFilePath ( )
{
return m_resourceFile;
}
/**
* Returns a script
*
* @param s Name of the script, valid values are
* - "preinst"
* - "postinst"
* - "prerm"
* - "postrm"
*
* @return Script or null if it doesn't exist
*/
public String getScript ( String s )
{
s = s.toLowerCase( );
if ( m_scriptMap.containsKey( s ) == false )
return null;
return m_scriptMap.get( s );
}
/**
* Extracts a package template and parses and replaces variables
* in filenames and files.
*
* @param o Output directory
* @param i Input file
*
* @throws Exception If recursion is too deep, a variable isn't defined or
* malformed meta data
* @throws IOException Error reading inputstream
* @throws ParseException Malformed JSON
* @throws FileNotFoundException Could not open input file
*/
public void doProcessTarGZip ( File o,
File i )
throws Exception,
IOException,
ParseException,
FileNotFoundException
{
FileInputStream fis = new FileInputStream( i );
GZIPInputStream gis = new GZIPInputStream( fis );
TarArchiveInputStream tis = new TarArchiveInputStream( gis );
// Remove any old data if any
if ( o.exists( ) == true )
o.delete( );
// Find and parse meta data, this should always be the
// first file, but it can' be assumed
while ( true )
{
ArchiveEntry e = tis.getNextEntry( );
if ( e == null )
break;
if ( e.getName( ).equals( ".meta/.meta" ) == false )
continue;
doParseMeta( tis );
break;
}
// Reset input
tis.close( );
gis.close( );
fis.close( );
fis = new FileInputStream( i );
gis = new GZIPInputStream( fis );
tis = new TarArchiveInputStream( gis );
// Process and extract files
while ( true )
{
File f;
ArchiveEntry e = tis.getNextEntry( );
if ( e == null )
break;
// Check if it's a script that we need to load and parse
if ( e.getName( ).contains( ".meta" ) == true )
{
if ( m_scriptMap.containsKey( e.getName( ) ) == true )
{
String name = m_scriptMap.get( e.getName( ) );
String script = m_varResolver.doParseStream( tis );
m_scriptMap.put( name, script );
m_scriptMap.remove( e.getName( ) );
}
continue;
}
// Store its permissions
String n = m_varResolver.doResolveString( e.getName( ) );
m_filemodeMap.put( n, ((TarArchiveEntry )e).getMode( ) );
// Directory ?
f = new File( o, n );
if ( e.isDirectory( ) == true )
{
if ( f.exists( ) == false )
f.mkdirs( );
continue;
}
// It's a file
if ( m_parseSet.contains( e.getName( ) ) == true )
m_varResolver.doParseCopyStream( f, tis );
else
BuilderUtil.getInstance( ).copyInputStreamToFile( f, tis, e.getSize( ) );
}
}
/**
* Parses the meta data and (might) set m_iconList, m_binaryDir,
* and m_parseSet
*
* @param i Inputstream which is meta data
*
* @throws Exception If recursion is too deep, a variable isn't defined or
* malformed meta data
* @throws IOException Error reading inputstream
* @throws ParseException Malformed JSON
*/
private void doParseMeta ( InputStream i )
throws Exception,
IOException,
ParseException
{
Reader r = new BufferedReader( new InputStreamReader( i ) );
JSONParser jParse = new JSONParser( );
JSONObject oMain = (JSONObject)jParse.parse( r );
// Check for program directory
if ( oMain.containsKey( "programDir" ) == true )
m_binaryPath = m_varResolver.doResolveString( (String)oMain.get( "programDir" ) );
// Check for parse file list
if ( oMain.containsKey( "parseList" ) )
{
for ( Object s : ((JSONArray)oMain.get( "parseList" )).toArray( ) )
m_parseSet.add( (String)s );
}
// Check for icon list
if ( oMain.containsKey( "iconList" ) )
{
for ( Object o : ((JSONArray)oMain.get( "iconList" )).toArray( ) )
{
String name;
JSONObject oIcon = (JSONObject)o;
// Get fields
if ( oIcon.containsKey( "type" ) == false )
throw new Exception( "Malformed metadata - No 'type' in icon" );
name = (String)oIcon.get( "type" );
if ( name.equals( "png" ) == true )
{
if ( oIcon.containsKey( "size" ) == false )
throw new Exception( "Malformed metadata - 'type' is png but no size" );
name += oIcon.get( "size" );
}
else if ( name.equals( "svg" ) == true )
{
// Don't need to do anything
}
else
throw new Exception( "Malformed metadata - Unrecognised icon type" );
// Associate type with path
if ( oIcon.containsKey( "path" ) == false )
throw new Exception( "Malformed metadata - No 'path' in icon" );
m_iconMap.put( name, m_varResolver.doResolveString( (String)oIcon.get( "path" )));
}
}
// Check for requires list
if ( oMain.containsKey( "requires" ) )
{
for ( Object s : ((JSONArray)oMain.get( "requires" )).toArray( ) )
m_requiresList.add( (String)s );
}
// Check for depends list
if ( oMain.containsKey( "depends" ) )
{
for ( Object s : ((JSONArray)oMain.get( "depends" )).toArray( ) )
m_dependsList.add( (String)s );
}
// Check scripts
String[] scriptList = { "preinst", "postinst", "prerm", "postrm" };
for ( String s : scriptList )
if ( oMain.containsKey( s ) )
m_scriptMap.put( (String)oMain.get( s ), s );
// Check for program file (path)
if ( oMain.containsKey( "programFile" ) )
m_programFile = m_varResolver.doResolveString( (String)oMain.get( "programFile" ) );
// Check for resource file (path)
if ( oMain.containsKey( "resourceFile" ) )
m_resourceFile = m_varResolver.doResolveString( (String)oMain.get( "resourceFile" ) );
}
}