/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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 org.bodington.server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.log4j.Logger;
import org.bodington.database.PrimaryKey;
import org.bodington.server.realm.Permission;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.ResourceTreeManager;
import org.bodington.server.resources.ResourceTreeQuota;
import org.bodington.server.resources.ResourceUtils;
import org.bodington.server.resources.UploadedFile;
import org.bodington.server.resources.UploadedFileManager;
import org.bodington.server.resources.UploadedFileNode;
import org.bodington.server.resources.UploadedFileSummary;
import org.bodington.util.FileMover;
import org.bodington.util.Scanner;
import org.bodington.util.ScannerResults;
import org.bodington.util.VirusScanner;
import org.bodington.util.html.HtmlScanner;

public class UploadedFileSessionImpl implements
    UploadedFileSession
{
    
    private static Logger log = Logger.getLogger(UploadedFileSessionImpl.class);
    
    /**
     * The file that contains the list of uploaded files.
     */
    private static final String FILELISTING_XML = "filelisting.xml";

    private BuildingSession buildingSession;
    
    public UploadedFileSessionImpl(BuildingSession buildingSession)
    {
        this.buildingSession = buildingSession;
    }
    
    public UploadedFileSummary copyFile( String current_pathname, String resource_pathname, String mime_type )
    throws BuildingServerException
    {
     return transferFile(current_pathname, resource_pathname, mime_type, false);
    }

    public UploadedFileSummary transferFile( String current_pathname, String resource_pathname, String mime_type )
    throws BuildingServerException
    {
         return transferFile(current_pathname, resource_pathname, mime_type, true);
    }

    private UploadedFileSummary transferFile( String current_pathname, String resource_pathname, String mime_type, boolean deleteOrig)
    throws BuildingServerException
    {
        if (current_pathname == null)
            throw new BuildingServerException( "No file supplied to upload.");
        if (resource_pathname == null)
            throw new BuildingServerException( "No location supplied to upload to.");
        
    	File source = new File( current_pathname );
    	if ( !source.exists() )
    		throw new BuildingServerException( "The file to be transferred into the system doesn't exist. [" + source + "]");

    	if ( source.length() < 1)
    		throw new BuildingServerUserException( "The uploaded file was empty");

    	Resource r = getResourceWithCheck();
    	if ( !r.checkPermission( Permission.UPLOAD ) )
    		throw new PermissionDeniedException( Permission.UPLOAD );
    	// there is one more UploadedFile than tokens because the root folder has no name
    	UploadedFile[] path;

    	QuotaMetadatum quota = getQuotaUpdate();

    	synchronized (quota.getLock())
    	{
 
    		// Database layer doesn't like nulls
            String actualMimeType = (mime_type == null || mime_type.length() == 0) ? 
                    BuildingServer.getInstance().getMimeType(resource_pathname)
                    :mime_type;

    		r.checkUpload( resource_pathname, actualMimeType );

    		// Check if we should be scanning uploads for viruses
    		if (BuildingContext.getProperty("upload.scan.virus", "false").equals(
    		"true"))
    		{
    			Scanner scanner = VirusScanner.getInstance();
    			if ( ! scanner.scan(source).isOk() )
    			{
    				log.info("Virus found in: "+ source.getAbsolutePath());
    				throw new BuildingServerUserException(
    				"Upload fail as a virus was detected");
    			}
    		}

    		// Don't do scanning if the tranfer is from one resource to another (eg a resource copy).
    		if (!current_pathname.startsWith(Resource.getWebPublishFolderRoot().getAbsolutePath()))
    		{
    			if (("text/html").equals(actualMimeType))
    			{
    				Scanner scanner = new HtmlScanner();
    				ScannerResults results = scanner.scan(source);
    				if ( ! results.isOk() )
    				{
    					BuildingServerUserException exception = new BuildingServerUserException(
    							"Problem transferring file");
    					exception.addMessages(results.getMessages());
    					throw exception;
    				}
    			}

    			// If it ends with .css (case-insensitive)
    			if ((CSSScanner.shouldScan(resource_pathname)))
    			{
    				Scanner scanner = new CSSScanner();
    				ScannerResults results = scanner.scan(source);
    				if ( ! results.isOk() )
    				{
    					BuildingServerUserException exception = new BuildingServerUserException(
    							"Problem transferring file");
    					exception.addMessages(results.getMessages());
    					throw exception;
    				}
    			}
    		}
    		java.sql.Timestamp now = new java.sql.Timestamp( System.currentTimeMillis() );
    		File target;
    		String[] tokens = UploadedFile.parsePath(resource_pathname);
    		if ( tokens.length < 1 )
    			throw new BuildingServerUserException(
    					"No destination file name was specified.");
    		path = new UploadedFile[tokens.length + 1];
    		String[] dirPath;

    		// build the necessary folders if they don't already exist
    		for ( int i=0; i<tokens.length; i++ )
    		{
    			dirPath = new String[i];
    			System.arraycopy( tokens, 0, dirPath, 0, i );

    			StringBuffer folderPath = new StringBuffer(resource_pathname
    					.length());
    			for (int pos = 0; pos < dirPath.length; pos++)
    			{
    				folderPath.append("/");
    				folderPath.append(dirPath[pos]);
    			}
    			path[i] = UploadedFile.findUploadedFile(createFolder(
    					folderPath.toString()).getPrimaryKey());

    		}

    		// now do the file at the end of the path
    		String filePath[] = new String[tokens.length];
    		System.arraycopy( tokens, 0, filePath, 0, tokens.length );
    		log.debug( "Checking file: " + filePath[filePath.length-1] );

    		// Create a lock on the directory in which this file will be created.
    		path[path.length - 1] = UploadedFile.findUploadedFileByPath(r
    				.getResourceId(), filePath);
    		if (path[path.length - 1] != null && path[path.length - 1].isFolder())
    			throw new BuildingServerUserException(
    					"Couldn't upload file - there is a folder with the same name.");

    		// create the object so we can ask it for the file
    		long existingSize = 0;
    		if (path[path.length - 1] == null)
    		{
    			log.debug("Making record");
    			path[path.length - 1] = new UploadedFile();
    			path[path.length - 1].setCreatedTime(now);
    			path[path.length - 1].setCreateUserId(getUserId());
    			path[path.length - 1].setFlags(0);
    			path[path.length - 1].setFolder(false);
    			// indicate that file is in process of being uploaded
    			// but nowhere do we check this!!
    			path[path.length - 1].setSize(1L);
    			path[path.length - 1].setBytesUploaded(0L);
    			path[path.length - 1].setName(tokens[tokens.length - 1]);
    		}
    		else
    		{
    		    existingSize = path[path.length-1].getSize();
    		}
    		
    		long quotaChange = -existingSize + source.length();
            checkQuota(quota, quotaChange);

    		path[path.length - 1].setMimeType((mime_type == null)?"":mime_type);
    		path[path.length - 1].setUpdatedTime(now);
    		path[path.length - 1].setUpdateUserId(getUserId());
    		path[path.length - 1].setDeleted(false);

    		if (path[path.length -1].isUnsaved())
    		{
    			try
    			{
    				UploadedFileManager manager = UploadedFileManager
    				.getInstance(r);
    				manager.addNode(path[path.length -2], path[path.length-1]);
    			}
    			finally
    			{
    				UploadedFileManager.release(r);
    			}
    		}
    		else
    		{
    			path[path.length - 1].save();
    		}

    		log.debug("Saved record in database.");
    		//last element in path - is the file
    		target = path[path.length - 1].getFile();
    		// delete current uploaded file
    		if (target.exists())
    		{
    			if (!target.isFile())
    				throw new BuildingServerUserException(
    						"Couldn't publish file because a file system object of the same name exists.");
    			target.delete();
    			if (target.exists())
    				throw new BuildingServerException(
    						"Couldn't delete old version of file in publishing area.");
    		}

    		// move file into position
    		try
    		{
    			if (deleteOrig)
    				FileMover.moveFile(source, target);
    			else
    				FileMover.copyFile(source, target);
    		}
    		catch (IOException ioex)
    		{
    			log.error(ioex.getMessage(),ioex);
    			throw new BuildingServerException("Couldn't move "
    					+ source.getAbsolutePath() + " to " + target.getAbsolutePath()
    					+ " in publishing area. " + ioex.getMessage());
    		}

    		// update size now the file is saved
    		path[path.length - 1].setSize(target.length());
    		path[path.length - 1].setBytesUploaded(target.length());
    		path[path.length - 1].save();


    		quota.adjustUsage(quotaChange);
    	}    
    	log.debug( "Created the file in the host file system" );

    	invalidateCache();

    	return path[path.length-1].getSummary();

    }

    private void checkQuota(QuotaMetadatum quota, long size) throws BuildingServerException, QuotaExceededException
    {
        if ( quota.hasQuota() && hasQuotas() && size > 0 && ( quota.getUsage() + size > quota.getQuota() ) )
            throw new QuotaExceededException(quota, size);
    }

    private boolean hasQuotas() throws BuildingServerException
    {
        if (getResourceWithCheck().checkPermission(Permission.SYSADMIN))
            return false;
        return true;
    }
   
    public UploadedFileSummary createFolder( String resource_pathname )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        if ( !r.checkPermission( Permission.UPLOAD ) )
            throw new PermissionDeniedException( Permission.UPLOAD );
            
        r.checkFolder( resource_pathname );
        
        
        java.sql.Timestamp now = new java.sql.Timestamp( System.currentTimeMillis() );
        File target;
        
        // should be capable of creating the root folder.
        
        String[] tokens = UploadedFile.parsePath(resource_pathname);
        String[] parent_tokens = (tokens.length==0)?null:new String[tokens.length-1];
        UploadedFile parent, folder;
            
        if ( tokens.length>0 )
        System.arraycopy( tokens, 0, parent_tokens, 0, tokens.length-1 );
        
        if ( tokens.length==0  )
            parent = null;
        else
        {
            parent = UploadedFile.findUploadedFileByPath( r.getResourceId(), parent_tokens );
            if ( parent == null)
            {
                if (tokens.length == 1)
                {
                    parent = UploadedFile.findUploadedFile(createFolder(null).getPrimaryKey());
                    if (parent == null)
                        throw new BuildingServerException( "Can't create root folder");
                }
                else
                    throw new BuildingServerException( "Can't find the folder to put the new folder in." );
            }
        }
        
        if ( parent!=null && !parent.isFolder() )
            throw new BuildingServerException( "Can't create folders inside files." );

        if ( parent!=null && parent.isDeleted() )
            throw new BuildingServerException( "Can't create a folder inside a deleted folder." );

        folder = UploadedFile.findUploadedFileByPath( r.getResourceId(), tokens );
        if ( folder != null && !folder.isFolder() )
            throw new BuildingServerException( "Can't create a folder with the same name as a file that exists already." );
            
        if ( folder != null )
            {
            if ( !folder.isDeleted() )
                return folder.getSummary();
                //throw new BuildingServerException( "The folder already exists." );
            folder.setDeleted( false );
            folder.setUpdatedTime( now );
            folder.setUpdateUserId( getUserId() );
            folder.save();
            }
        else
            {
            log.debug( "Making record" );

            folder = new UploadedFile(UploadedFile.FOLDER);
            folder.setCreateUserId( getUserId() );
            folder.setUpdateUserId( getUserId() );
            folder.setName( (parent==null)?"":tokens[tokens.length-1] );
            try
            {
                UploadedFileManager manager = UploadedFileManager.getInstance(r);
                manager.addNode(parent, folder);
            }
            finally
            {
                UploadedFileManager.release(r);
            }
            folder.save();
            
            target = folder.getFile();
            if ( !target.exists()  )
            {
            if ( !target.mkdir() )
                throw new BuildingServerException( "Couldn't create folder in publishing area." );
            }
        
            }
            
        invalidateCache();
        return folder.getSummary();
        }
        
    private Resource getResourceWithCheck() throws BuildingServerException
    {
        Resource r = Resource.findResource( getResourceId());
        if ( r==null )
            throw new BuildingServerException( "Can't find resource for this session." );
        return r;
    }

    public void createFile( String pathname, long size, String mime_type )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public void checkCreateFolder( String pathname )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public void checkCreateFile( String pathname, long size )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }
    
    public void deleteFile( String pathname )
    throws BuildingServerException
    {
    	UploadedFile uploaded;
    	Resource r = getResourceWithCheck();
    	uploaded = UploadedFile.findUploadedFileByPath(r.getPrimaryKey(),
    			pathname);
    	if (uploaded == null)
    		return;

    	QuotaMetadatum quota = getQuotaUpdate();
    	synchronized (quota.getLock())
    	{
    		try
    		{
    			UploadedFileManager.getInstance(r).delete(uploaded);
    		}
    		finally
    		{
    			UploadedFileManager.release(r);
    		}
    		quota.adjustUsage(-uploaded.getSize());
    	}
    	invalidateCache();
    }

    public void undeleteFile( String pathname, boolean recurse )
    throws BuildingServerException
    {
    	UploadedFile uploaded;
    	Resource r = getResourceWithCheck();
    	uploaded = UploadedFile.findUploadedFileByPath(r.getPrimaryKey(),
    			pathname);
    	if (uploaded == null)
    		return;

    	QuotaMetadatum quota = getQuotaUpdate();
    	synchronized (quota.getLock())
    	{
    		checkQuota(quota, uploaded.getSize());
    		try
    		{
    			UploadedFileManager.getInstance(r).restore(uploaded, recurse);
    		}
    		finally
    		{
    			UploadedFileManager.release(r);
    		}
    		quota.adjustUsage(uploaded.getSize());
    	}
    	invalidateCache();
    }
    
    public void renameFile( String pathname, String newname )
        throws BuildingServerException
        {
        if ( newname==null || newname.length()==0 )
            throw new BuildingServerUserException( "Empty names are not allowed." );
        if ( newname.indexOf( '/' )>=0 || newname.indexOf( '\\' )>=0  )
            throw new BuildingServerUserException( "File name with slashes are not allowed." );
        if ( newname.length()>255 )
            throw new BuildingServerUserException( "File names cannot exceed 255 chars in length." );
        
        Resource r = getResourceWithCheck();
        
        UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), pathname );
        if ( uf == null )
            throw new BuildingServerUserException( "The selected file doesn't exist." );
        if ( uf.getParentUploadedFileId() == null )
            throw new BuildingServerUserException( "You can't rename the root folder in a resource." );
            
        UploadedFile parent = UploadedFile.findUploadedFile( uf.getParentUploadedFileId() );
        if ( parent == null )
            throw new BuildingServerException( "Can't find the folder for the specified file/folder." );
        
        UploadedFile destuf = UploadedFile.findUploadedFileByParentAndName( 
                    r.getResourceId(), parent.getUploadedFileId(), newname );
        
        // if the destination exists and is a different record to the source
        // then you can't rename because the name is in use
        if ( destuf!=null && !destuf.getUploadedFileId().equals( uf.getUploadedFileId() ) )
            throw new BuildingServerUserException( "A file already exists with the proposed name." );
        
        String oldname = uf.getName();
        File target =  uf.getFile();
        
        if (("text/html").equals(BuildingServer.getInstance().getMimeType(newname)))
        {
            Scanner scanner = new HtmlScanner();
            ScannerResults results = scanner.scan(target);
            if ( ! results.isOk() )
            {
                BuildingServerUserException exception = new BuildingServerUserException("Problem transferring file");
                exception.addMessages(results.getMessages());
                throw exception;
            }
        }
        
        if (CSSScanner.shouldScan(newname))
        {
            Scanner scanner = new CSSScanner();
            ScannerResults results = scanner.scan(target);
            if ( ! results.isOk() )
            {
                throw new BuildingServerUserException(results.getMessages().toString());
            }
        }

        uf.setName( newname );
        File renamed = uf.getFile();
        

        
        if ( !target.renameTo( renamed ) )
            {
            uf.setName( oldname );
            uf.save();
            throw new BuildingServerException( "There was a problem renaming the file." );
            }
        // Reset the mime type if it has a custom one.
        uf.setMimeType("");
        uf.save();
        invalidateCache();
        }


    public void writeToFile( String pathname, long offset, byte[] data, int crc32 )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public void compareWithFile( String pathname, long offset, long length, int crc32 )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public int readFromFile( String pathname, long offset, byte[] data )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }
        
    public String getPublishedURL( PrimaryKey uf_id )
        throws BuildingServerException
        {
        getResourceWithCheck();
        UploadedFile uf;
        
        if ( uf_id == null )
            return null;
        
        uf = UploadedFile.findUploadedFile( uf_id );
        if ( uf!=null )
            {
            return uf.getURLInResource();
            }
            
        return null;
        }

    public String getRealURL( PrimaryKey uf_id )
        throws BuildingServerException
        {
        getResourceWithCheck();
        UploadedFile uf;
        
        uf = UploadedFile.findUploadedFile( uf_id );
        if ( uf!=null && !uf.isDeleted() && !uf.isFolder() && uf.isComplete() )
            {
            return uf.getRealURLInResource();
            }
            
        return null;
        }
        
    public String getFileMimeType( String pathname )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public void setFileMimeType( String pathname, String mime_type )
        throws BuildingServerException
        {
        throw new BuildingServerException( "Requested functionality not yet implemented." );
        }

    public UploadedFileSummary getFileSummary( String pathname )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), pathname );
        if ( uf == null )
            return null;
        return uf.getSummary();
        }

    public UploadedFileSummary getFileSummary( String[] path )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), path );
        if ( uf == null )
            return null;
        return uf.getSummary();
        }
    
    public UploadedFileSummary getFileSummary( PrimaryKey id )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        UploadedFile uf = UploadedFile.findUploadedFile( id );
        if ( uf == null )
            return null;
        // don't return data if the file belongs to another resource
        if ( uf.getResourceId().equals( r.getResourceId() ) )
            return uf.getSummary();
        return null;
        }

    public File getFile( PrimaryKey id )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        UploadedFile uf = UploadedFile.findUploadedFile( id );
        if ( uf == null )
            return null;
        // don't return data if the file belongs to another resource
        if ( uf.getResourceId().equals( r.getResourceId() ) )
            return uf.getFile();
        return null;
        }
        

    public DefaultMutableTreeNode getFileAndDescendentSummaryTree( String pathname, boolean omit_deleted  )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        Enumeration enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, omit_deleted );
            
        UploadedFile file, parent_file;
        UploadedFileNode root, parent, current;
        
        // root tree node is empty - all uploaded files are descendents of it
        root = new UploadedFileNode();
        parent = root;
        
        // if there isn't even a root folder put in an
        // unsaved one.
        if ( !enumeration.hasMoreElements() )
            {
            file = new UploadedFile();
            file.setParentUploadedFileId( null );
            file.setName( "" );
            file.setFolder( true );
            current = new UploadedFileNode();
            current.setUserObject( file );
            parent.add( current );
            return root;
            }
        
        while ( enumeration.hasMoreElements() )
            {
            file = (UploadedFile)enumeration.nextElement();
            current = new UploadedFileNode();
            current.setUserObject( file );
            
            // go up the tree to find the parent of this node
            // assumes that entries are sorted by left_index already
            // if parent isn't found it is added to the tree root 
            // (not the same as the file system root)
            parent_file = (UploadedFile)parent.getUserObject();
            while ( parent_file != null && 
                    !parent_file.getUploadedFileId().equals( file.getParentUploadedFileId() ) )
                {
                parent = (UploadedFileNode)parent.getParent();
                parent_file = (UploadedFile)parent.getUserObject();
                }
                
            parent.add( current );
            parent = current;
            }

            
        root.sortByName();
        
        return root;
        }


    public UploadedFileSummary[] getFileAndDescendentSummaries( String pathname, boolean omit_deleted  )
        throws BuildingServerException
        {
        try
            {
        DefaultMutableTreeNode root = getFileAndDescendentSummaryTree( pathname, omit_deleted  );
        DefaultMutableTreeNode node;
        
        Enumeration enumeration = root.preorderEnumeration();
        
        Vector list = new Vector();
        while ( enumeration.hasMoreElements() )
            {
            node = (DefaultMutableTreeNode)enumeration.nextElement();
            if ( node.getUserObject() != null )
                list.addElement( node.getUserObject() );
            }
        UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
        for ( int i=0; i< list.size(); i++ )
            summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();

        return summaries;
            }
        catch ( Exception ex )
            {
            log.error( ex.getMessage(), ex );
            }
        return null;
        
        /*
        Resource r = getResourceWithCheck();
        Enumeration enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, omit_deleted );
        Vector list = new Vector();
        while ( enumeration.hasMoreElements() )
            list.addElement( enumeration.nextElement() );
        UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
        for ( int i=0; i< list.size(); i++ )
            summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();
        return summaries;
        */
        }

    public UploadedFileSummary[] getFileAndAncestorSummaries( String pathname )
        throws BuildingServerException
        {
        Resource r = getResourceWithCheck();
        Enumeration enumeration = UploadedFile.findUploadedFileAncestors( r.getResourceId(), pathname );
        Vector list = new Vector();
        while ( enumeration.hasMoreElements() )
            list.addElement( enumeration.nextElement() );
        UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
        for ( int i=0; i< list.size(); i++ )
            summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();
        return summaries;
        }
    
    public void transferZipFile( ZipFile archive, String startfilename )
    throws BuildingServerException
    {
    ZipEntry zipentry;
    String file_name;
    StringBuffer dest_filename = new StringBuffer();
    InputStream zipinput;
    FileOutputStream fout;
    File file;
    int b;
    
    try
        {
        for ( Enumeration enumeration=archive.entries(); enumeration.hasMoreElements(); )
            {
            zipentry = (ZipEntry)enumeration.nextElement();
            log.debug( "ZIP Entry = [" + zipentry + "]" );

            //ignore directories - don't create empty folders
            file_name = zipentry.getName();
            if ( zipentry.isDirectory() || file_name==null )
                continue;

            log.debug( "ZIP file = [" + file_name + "]" );

            dest_filename.setLength( 0 );
            dest_filename.append( startfilename );
            dest_filename.append( file_name  );


            zipinput = archive.getInputStream( zipentry );
            if ( zipinput==null )
                throw new BuildingServerException( "Unreadable ZIP file entry: " + zipentry.toString() );
                            
            file = BuildingContext.createTempFile( "zipentry", ".bin" );
            fout = new FileOutputStream( file );
            while ( (b=zipinput.read()) >= 0 )
                fout.write( b );
            fout.close();
            zipinput.close();
            transferFile( file.getAbsolutePath(), dest_filename.toString(), null );
            
            }
        archive.close();
        (new File( archive.getName() )).delete();
        }
    catch ( Exception ex )
        {
        log.error( ex.getMessage(), ex );
        throw new BuildingServerException( "Problem unpacking package: " + ex.getMessage() );
        }
    }

    private void invalidateCache()
    {
    try
        {
        Resource r = getResourceWithCheck();
        File folder = r.getGeneratedFileFolder();
        File lfile = new File( folder, FILELISTING_XML );
        
        if ( lfile.exists() )
            lfile.delete();
        }
    catch ( Exception  ex )
        {
        log.error( ex.getMessage(), ex );
        }
        
        // BuildingSession caches uploaded files data.
        try
        {
            buildingSession.invalidateCache();
    }
        catch (BuildingServerException e)
        {
            log.error( e.getMessage(), e);
        }
    }

    public Quota getQuota() throws BuildingServerException 
    {
        final Permission permissions[] = new Permission[]{Permission.UPLOAD, Permission.MANAGE};
        if (ResourceUtils.anyPermission(getResourceWithCheck(), permissions))
        {
            QuotaMetadatum quota = getQuotaUpdate();
            return (quota.hasQuota())?quota:null;
        }
        throw new PermissionDeniedException(permissions);
    }

    public void setQuota(long quota) throws BuildingServerException
    {
        Resource resource = getResourceWithCheck();
        if (!resource.checkPermission(Permission.SYSADMIN))
            throw new PermissionDeniedException(Permission.SYSADMIN);
        
        getQuotaUpdate().setQuota(quota);        
    }
    
    public void createQuota(long quota) throws BuildingServerException
    {
        Resource resource = getResourceWithCheck();
        if (!resource.checkPermission(Permission.MANAGE))
            throw new PermissionDeniedException(Permission.MANAGE);
        
        QuotaMetadatum quotaMetadatum = getQuotaUpdate();
        if (quotaMetadatum.hasQuota())
            throw new BuildingServerException("Quota already exists.");
        
        quotaMetadatum.setQuota(quota);
    }
    
    public boolean checkFileQuota() throws BuildingServerException
    {
        Resource resource = getResourceWithCheck();
        if (!resource.checkPermission(Permission.SYSADMIN))
            throw new PermissionDeniedException(Permission.SYSADMIN);
        
        QuotaMetadatum quota = QuotaFactory.getQuota(resource, QuotaFactory.FILES);
        if (!quota.hasQuota()) {
            return true;
        }
            
        Object lock = quota.getLock();
        synchronized (lock)
        {
            int actual = sumFileQuota(quota.getResource(), quota);
            if (actual != quota.getUsage()) {
                log.info("Incorrect quota for "+ resource.getFullName()+ " usage: "+ quota.getUsage()+ " actual: "+ actual);
                quota.adjustUsage( -(quota.getUsage()-actual));
                return false;
            } else {
                log.debug("Correct quota for "+ resource.getFullName()+ " usage: "+ quota.getUsage());
                return true;
            }
        }
    }
    
    /**
     * Walk down the resource tree adding up all the quota. Stop when we reach
     * the leaves of the tree or when another quota is encountered.
     * @param currentResource Current resource
     * @param quota Quota to calculate against.
     * @return Total usage in bytes.
     */
    private int sumFileQuota(Resource currentResource, Quota quota) throws BuildingServerException{ 
        Quota currentQuota = QuotaFactory.getQuota(currentResource, QuotaFactory.FILES);
        if (!currentQuota.getResource().equals(quota.getResource())) return 0;
        UploadedFileSession session = BuildingSessionManagerImpl.getSession(
            currentResource).getUploadedFileSession();
        UploadedFileSummary[] summaries = session
        .getFileAndDescendentSummaries((String) null, true);
        int sum = 0;
        for (int i = 0; i < summaries.length; i++)
        {
            sum += summaries[i].getSize();
        }
        Enumeration children = currentResource.findChildren();
        while (children.hasMoreElements())
        {
            sum += sumFileQuota((Resource)children.nextElement(), quota);
        }
        return sum;
    }

    /**
     * Gets the quota for the current resource that can be managed internally.
     */
    private QuotaMetadatum getQuotaUpdate() throws BuildingServerException
    {
        return QuotaFactory.getQuota(getResourceWithCheck(), QuotaFactory.FILES);
    }

    private PrimaryKey getResourceId() throws BuildingServerException
    {
        return buildingSession.getResourceId();
    }

    private PrimaryKey getUserId() throws BuildingServerException
    {
        return buildingSession.getUserId();
}
}
