/*
* HeadsUp Agile
* Copyright 2009-2014 Heads Up Development Ltd.
*
* 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/>.
*/
package org.headsupdev.agile.framework;
import org.headsupdev.agile.web.components.OnePressAjaxSubmitButton;
import org.headsupdev.support.java.FileUtil;
import org.headsupdev.support.java.IOUtil;
import org.headsupdev.support.java.compression.GZipFile;
import org.headsupdev.support.java.compression.TarFile;
import org.headsupdev.agile.api.*;
import org.headsupdev.agile.api.logging.Logger;
import org.headsupdev.agile.security.permission.AdminPermission;
import org.headsupdev.agile.core.UpdateDetails;
import org.headsupdev.agile.core.DefaultManager;
import org.headsupdev.agile.web.SystemEvent;
import org.headsupdev.agile.web.HeadsUpSession;
import org.headsupdev.agile.storage.HibernateUtil;
import org.headsupdev.agile.runtime.HeadsUpRuntime;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.Session;
import org.apache.wicket.model.Model;
import org.wicketstuff.progressbar.ProgressBar;
import org.wicketstuff.progressbar.ProgressionModel;
import org.wicketstuff.progressbar.Progression;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.io.*;
/**
* The main updating panel that displays progress as it performs the update.
*
* @author Andrew Williams
* @version $Id$
* @since 1.0
*/
public class UpdatingPanel extends Panel
{
private int progress = 0;
File outFile;
private HeadsUpRuntime runtime;
private Logger log = Manager.getLogger( getClass().getName() );
public UpdatingPanel( String id, HeadsUpRuntime runtime )
{
super( id );
this.runtime = runtime;
// declare this object early so we do not look it up while restarting
final Model restartingModel = new Model<String>() {
public String getObject() {
return "Restarting...";
}
};
final WebMarkupContainer run = new WebMarkupContainer( "runupdate" );
run.setOutputMarkupId( true );
final Label done = new Label( "doneupdate", "Updating will restart the software. " +
"Please note that after it restarts there may be additional setup steps." );
done.setOutputMarkupId( true );
final ProgressBar bar = new ProgressBar( "bar", new ProgressionModel()
{
protected Progression getProgression()
{
return new Progression( progress );
}
} )
{
protected void onFinished( AjaxRequestTarget target )
{
target.addComponent( run.setVisible( false ) );
target.addComponent( this.setVisible( false ) );
target.addComponent( done.setVisible( true ) );
done.setDefaultModel( restartingModel );
}
};
final Form updateForm = new Form( "updatenow" );
updateForm.add( new OnePressAjaxSubmitButton( "submit", updateForm )
{
public void onSubmit( AjaxRequestTarget target, Form form )
{
target.addComponent( done.setDefaultModel( new Model<String>()
{
public String getObject()
{
return "Please Wait...";
}
} ) );
try
{
final UpdateDetails update = getUpdate();
final long length = update.getLength();
URL download = new URL( update.getFile() );
URLConnection conn = download.openConnection();
conn.connect();
conn = followRedirects( conn );
if ( length > -1 ) {
target.addComponent( this.setVisible( false ) );
target.addComponent( run.setVisible( true ) );
target.addComponent( bar.setVisible( true ) );
bar.start( target );
}
// declare the thread now so we do not try to look it up after our resources have been moved
final Thread restartThread = new Thread() {
public void run() {
try
{
sleep( 1000 );
} catch (InterruptedException e) {
// just restart anyway
}
try
{
UpdatingPanel.this.runtime.restart();
}
catch ( Exception e )
{
log.error( "Failed to restart container", e );
}
}
};
final URLConnection connection = conn;
new Thread() { public void run() {
try {
byte[] buffer = new byte[4096];
long downloaded = 0;
if ( connection.getURL().getProtocol().equals( "file" ) )
{
outFile = new File( connection.getURL().getFile() );
progress = 99;
}
else
{
outFile = new File( org.headsupdev.agile.api.util.FileUtil.getTempDir(),
new File( update.getFile() ).getName() );
InputStream in = connection.getInputStream();
OutputStream out = new FileOutputStream( outFile );
int chunk;
while ( ( chunk = in.read( buffer ) ) > -1 ) {
downloaded += chunk;
out.write( buffer, 0, chunk );
if ( length > 0 )
{
int prog = (int) ( ( (double) downloaded / length ) * 100 );
if ( prog > 99 ) {
prog = 99;
}
progress = prog;
}
}
in.close();
out.close();
}
outFile = new TarFile( new GZipFile( outFile ).expand( true ) ).expand();
// pull it out of the dir created
File tmpDir = new File( outFile.getAbsolutePath() + "_dir" );
outFile.renameTo( tmpDir );
File[] children = tmpDir.listFiles();
// push new to a direct child of our root for later
outFile = new File( outFile.getParentFile(), outFile.getName() );
children[0].renameTo( outFile );
tmpDir.delete();
// add a system event before we start the moving
String version = new File( update.getFile() ).getName();
version = version.substring( 0, version.indexOf( ".tar.gz" ) );
Event event = new SystemEvent( Manager.getStorageInstance().getGlobalConfiguration().getProductName() + " upgraded to " +
version, Manager.getStorageInstance().getGlobalConfiguration().getProductName() + " has been upgraded to " + version +
" - congratulations", "<h2>" + Manager.getStorageInstance().getGlobalConfiguration().getProductName() + " version " +
version + "</h2>" + update.getDetails() );
Manager.getStorageInstance().addEvent( event );
Manager.getInstance().fireEventAdded(event);
// override some permissions as they are lost when we unpack in java
Process chmodProcess = null;
try
{
chmodProcess = Runtime.getRuntime().exec( "chmod -R +x " + new File( outFile, "bin" ).getAbsolutePath() );
chmodProcess.waitFor();
}
catch ( InterruptedException e )
{
// never mind
}
finally
{
if ( chmodProcess != null )
{
IOUtil.close( chmodProcess.getOutputStream() );
IOUtil.close( chmodProcess.getErrorStream() );
IOUtil.close( chmodProcess.getInputStream() );
chmodProcess.destroy();
}
}
updateProperties( new File( new File( outFile, "conf" ), "config.properties" ) );
File mainDir = new File( "." );
File backupDir = new File( mainDir, "backup" );
if ( backupDir.exists() ) {
for ( File child : backupDir.listFiles() ) {
FileUtil.delete( child );
}
} else {
backupDir.mkdir();
}
for ( File child : mainDir.listFiles() ) {
if ( child.equals( outFile ) ) {
continue;
}
child.renameTo( new File( backupDir, child.getName() ) );
}
for ( File child : outFile.listFiles() ) {
child.renameTo( new File( mainDir, child.getName() ) );
}
outFile.delete();
progress = 100;
restartThread.start();
}
catch ( IOException e )
{
log.error( "Failed to unpack the downloaded update", e );
}
} }.start();
}
catch ( IOException e )
{
log.error( "Exception trying to upgrade system", e );
}
}
} );
updateForm.add( bar );
bar.setVisible( false );
updateForm.add( run );
run.setVisible( false );
updateForm.add( done );
add( updateForm );
setVisible( hasUpdate() &&
Manager.getSecurityInstance().userHasPermission( ( (HeadsUpSession) Session.get() ).getUser(),
new AdminPermission(), null ) );
}
protected UpdateDetails getUpdate()
{
return ( (DefaultManager) Manager.getInstance() ).getAvailableUpdates().get( 0 );
}
protected boolean hasUpdate()
{
return ( (DefaultManager) Manager.getInstance() ).getAvailableUpdates().size() > 0;
}
private void updateProperties( File file )
{
java.util.Properties properties = new java.util.Properties();
// here we check that the file exists, we are preparing for a new config system so the
// first upgrade will not have the file present
if ( file.exists() )
{
InputStream in = null;
try
{
in = new FileInputStream( file );
properties.load( in );
}
catch ( IOException e )
{
log.error( "Failed reading configuration for upgrade", e );
return;
}
finally
{
if ( in != null )
{
try
{
in.close();
}
catch ( IOException e )
{
// ignore
}
}
}
}
java.util.Properties current = HibernateUtil.properties;
for ( Object o : current.keySet() ) {
String key = o.toString();
properties.setProperty( key, current.getProperty( key ) );
}
OutputStream out = null;
try
{
out = new FileOutputStream( file );
properties.store( out, "Installed current properties" );
}
catch ( IOException e )
{
log.error( "Failed writing configuration for upgrade", e );
}
finally
{
if ( out != null )
{
try
{
out.close();
}
catch ( IOException e )
{
// ignore
}
}
}
}
protected URLConnection followRedirects( URLConnection connection )
throws IOException
{
if ( !( connection instanceof HttpURLConnection ) )
{
return connection;
}
// follow redirects that Java does not handle (http: -> https: for example)
if ( ( (HttpURLConnection) connection).getResponseCode() == 301 || ( (HttpURLConnection) connection).getResponseCode() == 302 )
{
String newLocation = connection.getHeaderField( "Location" );
URL download = new URL( newLocation );
connection = download.openConnection();
connection.connect();
}
return connection;
}
}