/*
* Created on Dec 10, 2004
*
*/
package com.rectang.xsm.site;
import java.util.*;
import java.io.*;
import com.rectang.xsm.util.StreamGobbler;
import org.jdom.*;
import com.rectang.xsm.*;
import com.rectang.xsm.io.*;
import com.rectang.xsm.util.StringUtils;
import org.headsupdev.support.java.IOUtil;
/**
* @author aje
*
* Maintain a cache of the site heirarchy and configuration.
*/
public class Site
implements Serializable
{
private static final int VERSION = 11;
private int version = 1; // all sites more recent than version 1 are tagged
private String stylesheet, layout, index, id, news, login;
private String title, description, keywords;
private int type;
private boolean register;
private long mtime = 0, connMtime = 0;
private List admins, editors, technologies;
private String rootDir, rootUrl, prefixUrl;
private HierarchicalPage rootPage;
private long quota;
private String quotaIncludes;
private String remoteUser, remotePassword, remoteHost;
private List visitors;
public static final int LOCAL = 1;
public static final int SSH = 2;
public static final int FTP = 3;
public Site( String site )
{
if ( site == null || site.equals( "" )
|| !getSiteList().contains( site.toLowerCase() ) )
{
return;
}
id = site.toLowerCase();
touch();
}
private void loadConnection()
{
RemoteDocument doc = RemoteDocument.getDoc( this, "/connection", true );
connMtime = doc.getModifiedTime();
try
{
Element root = doc.getRootElement();
String typeStr = root.getAttributeValue( "type" );
Element optional;
if ( typeStr != null && typeStr.equals( "ssh" )
&& ((optional = root.getChild( "ssh" )) != null) )
{
type = SSH;
remoteUser = optional.getChildText( "username" );
remoteHost = optional.getChildText( "host" );
}
else if ( typeStr != null && typeStr.equals( "ftp" )
&& ((optional = root.getChild( "ftp" )) != null) )
{
type = FTP;
remoteUser = optional.getChildText( "username" );
remotePassword = optional.getChildText( "password" );
remoteHost = optional.getChildText( "host" );
}
else
{
type = LOCAL;
}
rootDir = root.getChildText( "rootDir" );
if ( rootDir == null )
{
throw new Exception( "Corrupt site file - no rootDir" );
}
rootUrl = root.getChildText( "rootUrl" );
if ( rootUrl == null )
{
throw new Exception( "Corrupt site file - no rootUrl" );
}
prefixUrl = root.getChildText( "prefixUrl" );
if ( prefixUrl == null )
{
prefixUrl = "";
}
String techString = root.getAttributeValue( "provides" );
technologies = StringUtils.stringToList( techString );
String quotaStr = root.getChildText( "quota" );
if ( quotaStr == null )
{
quotaStr = "0";
}
quota = Long.parseLong( quotaStr );
quotaIncludes = root.getChildText( "quotaIncludes" );
}
catch ( Exception e )
{
/* FIXME cannot error here */
e.printStackTrace();
}
}
private void touchConnection()
{
long newTime = (new File( RemoteDocument.calculateFileName( this, "/connection", true ) )).lastModified();
if ( newTime > connMtime )
{
loadConnection();
}
}
private void load()
{
RemoteDocument doc = RemoteDocument.getDoc( this, "/site", true );
mtime = doc.getModifiedTime();
try
{
Element root = doc.getRootElement();
try
{
version = Integer.parseInt( root.getAttributeValue( "version" ) );
}
catch ( NumberFormatException e )
{
/* use default site version (1) */
}
admins = StringUtils.stringToList( root.getAttributeValue( "admin" ) );
editors = StringUtils.stringToList( root.getAttributeValue( "edit" ) );
news = root.getAttributeValue( "newsSource" );
if ( news == null )
{
news = "";
}
login = root.getAttributeValue( "login" );
if ( login == null )
{
login = "";
}
register = notNull( root.getAttributeValue( "register" ) ).toLowerCase().equals( "true" );
title = notNull( root.getChildText( "title" ) );
description = notNull( root.getChildText( "description" ) );
keywords = notNull( root.getChildText( "keywords" ) );
stylesheet = notNull( root.getChildText( "stylesheet" ) );
layout = notNull( root.getChildText( "layout" ) );
Element pages = root.getChild( "pages" );
index = pages.getAttributeValue( "default" );
this.rootPage = new HierarchicalPage( this, null, "/", false );
rootPage.addSubPages( initPages( pages.getChildren(), rootPage ) );
}
catch ( Exception e )
{
e.printStackTrace();
}
}
public void touch()
{
touchConnection();
long newTime = (new File( RemoteDocument.calculateFileName( this, "/site", true ) )).lastModified();
if ( newTime > mtime )
{
load();
}
}
public boolean save()
{
try
{
RemoteDocument doc = RemoteDocument.getDoc( this, "/site", true );
Element site = new Element( "xsmsite" );
site.setAttribute( "version", "" + version );
site.setAttribute( "admin", StringUtils.listToString( getAdmins() ) );
site.setAttribute( "edit", StringUtils.listToString( getEditors() ) );
site.setAttribute( "login", login );
site.setAttribute( "register", String.valueOf( register ) );
site.setAttribute( "newsSource", news );
site.addContent( new Element( "title" ).setText( this.getTitle() ) );
site.addContent( new Element( "description" ).setText( this.getDescription() ) );
site.addContent( new Element( "keywords" ).setText( this.getKeywords() ) );
site.addContent( new Element( "stylesheet" ).setText( this.getStylesheet() ) );
site.addContent( new Element( "layout" ).setText( this.getLayout() ) );
Element pages = new Element( "pages" );
pages.addContent( savePages( this.getPages() ) );
pages.setAttribute( "default", this.getDefault() );
site.addContent( pages );
doc.setRootElement( site );
boolean ret = doc.save();
/* TODO - do we really need this to avoid what seems to be an mtime
* race condition */
if ( ret )
{
load();
}
return ret;
}
catch ( Exception e )
{
e.printStackTrace();
return false;
}
}
public boolean publishTheme()
{
getPublishedDoc( "_theme" ).mkdir();
if ( getStylesheet().equals( "custom" ) )
{
File style = new File( XSM.getConfig().getSiteTemplateDir( this ), "style.css" );
getPublishedDoc( "_theme/style.css" ).uploadFile( style, false );
}
else
{
InputStream style = null;
OutputStream out = null;
try
{
style = getClass().getClassLoader().getResourceAsStream(
"/com/rectang/xsm/publish/style/" + getStylesheet() + ".css" );
out = getPublishedDoc( "_theme/style.css" ).getOutputStream();
IOUtil.copyStream( style, out );
}
catch ( IOException e )
{
e.printStackTrace();
return false;
}
finally
{
IOUtil.close( style );
IOUtil.close( out );
}
}
if ( getLayout().equals( "custom" ) )
{
File style = new File( XSM.getConfig().getSiteTemplateDir( this ), "layout.css" );
getPublishedDoc( "_theme/layout.css" ).uploadFile( style, false );
}
else
{
InputStream style = null;
OutputStream out = null;
try
{
style = getClass().getClassLoader().getResourceAsStream(
"/com/rectang/xsm/publish/layout/" + getLayout() + ".css" );
out = getPublishedDoc( "_theme/layout.css" ).getOutputStream();
IOUtil.copyStream( style, out );
}
catch ( IOException e )
{
e.printStackTrace();
return false;
}
finally
{
IOUtil.close( style );
IOUtil.close( out );
}
}
return true;
}
public Map publish( UserData user )
{
Map results = new HashMap();
results.put( " _theme", Boolean.valueOf( publishTheme() ) );
publish_files( getPages(), user, results );
return results;
}
private static void publish_files( List pageList, UserData user, Map results )
{
Iterator pages = pageList.iterator();
while ( pages.hasNext() )
{
Page next = (Page) pages.next();
if ( !next.isPublishable() )
{
continue;
}
Boolean ok = Boolean.FALSE;
try
{
if ( next.publish( user ) )
{
ok = Boolean.TRUE;
}
}
catch ( Throwable e )
{
// TODO report this in the return...
// ok = "ERROR " + e.getMessage();
e.printStackTrace();
}
results.put( next.getPath(), ok );
if ( next instanceof HierarchicalPage )
{
publish_files( ((HierarchicalPage) next).getSubPages(), user, results );
}
}
}
private Vector initPages( List pages, HierarchicalPage parent )
{
if ( pages.size() == 0 )
{
return null;
}
Vector ret = new Vector();
Iterator list = pages.iterator();
while ( list.hasNext() )
{
Element next = (Element) list.next();
String hiddenStr = next.getAttributeValue( "hidden" );
boolean hidden = hiddenStr != null && hiddenStr.equals( "true" );
Page newPage;
if ( next.getName().equals( "page" ) )
{
newPage = new DocumentPage( this, parent, next.getChild( "title" ).getText(),
hidden );
}
else if ( next.getName().equals( "link" ) )
{
newPage = new LinkPage( this, parent, next.getChildText( "title" ), hidden );
((LinkPage) newPage).setLink( next.getAttributeValue( "url" ) );
}
else if ( next.getName().equals( "title" ) )
{
continue; // ignore title elements, we are just interested in other nodes
}
else
{
System.err.println( "Unknown Page type " + next.getName() );
continue;
}
if ( newPage instanceof HierarchicalPage )
{
((HierarchicalPage) newPage).addSubPages( initPages( next.getChildren(),
(HierarchicalPage) newPage ) );
}
newPage.setSlug( next.getAttributeValue( "slug" ) );
ret.add( newPage );
}
return ret;
}
private List savePages( List pageList )
{
Iterator list = pageList.iterator();
if ( !list.hasNext() )
{
return null;
}
ArrayList ret = new ArrayList();
while ( list.hasNext() )
{
Page next = (Page) list.next();
Element newPage = null;
if ( next instanceof DocumentPage )
{
newPage = new Element( "page" );
}
else if ( next instanceof LinkPage )
{
newPage = new Element( "link" );
newPage.setAttribute( "url", next.getLink() );
}
else
{
System.err.println( "Unable to save reference to page " + next.getTitle() );
continue;
}
newPage.setAttribute( "hidden", String.valueOf( next.getHidden() ) );
newPage.addContent( new Element( "title" ).setText( next.getTitle() ) );
if ( next instanceof HierarchicalPage )
{
newPage.addContent( savePages( ((HierarchicalPage) next).getSubPages() ) );
}
if ( !next.getFile().equals( next.getSlug() ) )
{
newPage.setAttribute( "slug", next.getSlug() );
}
ret.add( newPage );
}
return ret;
}
public boolean exists()
{
return id != null;
}
private static String notNull( String in )
{
if ( in == null )
{
return "";
}
return in;
}
public int getVersion()
{
return version;
}
public static int getCurrentVersion()
{
return VERSION;
}
public void setVersion( int version )
{
this.version = version;
}
public boolean needsUpgrade()
{
return getCurrentVersion() > getVersion();
}
public String getId()
{
return id;
}
public String getTitle()
{
return title;
}
/**
* Get the type of site we are referencing. Currenly LOCAL or SSH.
* @return the site type;
*/
public int getType()
{
return type;
}
public String getStylesheet()
{
return stylesheet;
}
public String getLayout()
{
return layout;
}
public String getDefault()
{
return index;
}
public List getPages()
{
if ( rootPage == null )
{
return null;
}
return rootPage.getSubPages();
}
public HierarchicalPage getRootPage()
{
return rootPage;
}
public Iterator getAllPages()
{
Vector pages = new Vector();
addPages( pages, getRootPage().getSubPages() );
return pages.iterator();
}
private void addPages( List pages, List pageList )
{
Iterator addIter = pageList.iterator();
while ( addIter.hasNext() )
{
Page next = (Page) addIter.next();
pages.add( next );
if ( next instanceof HierarchicalPage )
{
addPages( pages, ((HierarchicalPage) next).getSubPages() );
}
}
}
public Page getPage( String path )
{
Page ret = this.rootPage;
if ( path == null || path.equals( "" ) || path.equals( "/" ) )
{
return ret;
}
String[] split = path.split( "/" );
String searchPath = "";
for ( int i = 0; i < split.length; i++ )
{
if ( split[i].equals( "" ) )
{
continue;
}
boolean found = false;
if ( ret instanceof HierarchicalPage )
{
searchPath += "/" + split[i];
Iterator pages = ((HierarchicalPage) ret).getSubPages().iterator();
while ( pages.hasNext() && !found )
{
Page node = (Page) pages.next();
if ( node.getPath().equals( searchPath ) )
{
ret = node;
found = true;
}
}
}
if ( !found )
{
return null;
}
}
return ret;
}
/**
* Return a list of provided technologies for this server (php, apache e.g.).
* The Objects are simply Strings.
*
* @return Returns the site's technology list
*/
public List getTechnologies()
{
return technologies;
}
/**
* @return Returns the admin list
*/
public List getAdmins()
{
return admins;
}
/**
* @param admin The admin to add
*/
public void addAdmin( String admin )
{
this.admins.add( admin );
}
/**
* @param admin The admin to remove
*/
public void delAdmin( String admin )
{
if ( this.admins.contains( admin ) )
{
this.admins.remove( admin );
}
}
/**
* @return Returns the site's global editor list
*/
public List getEditors()
{
return editors;
}
/**
* @param editor The site's global editor to add
*/
public void addEditor( String editor )
{
this.editors.add( editor );
}
/**
* @param editor The site's global editor to remove
*/
public void delEditor( String editor )
{
if ( this.editors.contains( editor ) )
{
this.editors.remove( editor );
}
}
public void setTitle( String title )
{
this.title = title;
}
/**
* @param layout The layout to set.
*/
public void setLayout( String layout )
{
this.layout = layout;
}
/**
* @param stylesheet The stylesheet to set.
*/
public void setStylesheet( String stylesheet )
{
this.stylesheet = stylesheet;
}
public void setDefault( String def )
{
index = def;
}
/**
* @return Returns the rootDir.
*/
public String getRootDir()
{
return rootDir;
}
public String getUniqueID()
{
return id;
}
/**
* @return Returns the rootUrl.
*/
public String getRootUrl()
{
return rootUrl;
}
/**
* @return Returns the prefix url.
*/
public String getPrefixUrl()
{
return prefixUrl;
}
public PublishedFile getPublishedDoc( String f )
{
if ( type == SSH )
{
return new SshPublishedFile( this, f );
}
if ( type == FTP )
{
return new FtpPublishedFile( this, f );
}
return new LocalPublishedFile( this, f );
}
/**
* @return Returns the page for the sites news source.
*/
public String getNewsSource()
{
return news;
}
/**
* The link to the login page (if one exists) if left blank will generate a
* URL to the XSM instance.
*
* @return Returns the page for the sites login screen.
*/
public String getLogin()
{
return login;
}
/**
* @param news The page for the sites news source to set.
*/
public void setNewsSource( String news )
{
this.news = news;
}
/**
* Return whether or not this site allows registration.
*
* @return if this site allows registrations
*/
public boolean canRegister()
{
return register;
}
/**
* @param canRegister whether or not we wish new users to register themselves
*/
public void setCanRegister( boolean canRegister )
{
this.register = canRegister();
}
/**
* @return Returns the remoteHost (if applicable).
*/
public String getRemoteHost()
{
return remoteHost;
}
/**
* @return Returns the remoteUser (if applicable).
*/
public String getRemoteUser()
{
return remoteUser;
}
/**
* @return Returns the remotePassword (if applicable).
*/
public String getRemotePassword()
{
return remotePassword;
}
/**
* @return Returns the description.
*/
public String getDescription()
{
return description;
}
/**
* @param description The description to set.
*/
public void setDescription( String description )
{
this.description = description;
}
/**
* @return Returns the keywords.
*/
public String getKeywords()
{
return keywords;
}
/**
* @param keywords The keywords to set.
*/
public void setKeywords( String keywords )
{
this.keywords = keywords;
}
public long getQuota()
{
return quota;
}
public String getQuotaIncludes()
{
return quotaIncludes;
}
public static Vector getSiteList()
{
File[] list = new File( XSM.getConfig().getDataDir() ).listFiles();
Vector ret = new Vector();
if ( list != null )
{
for ( int i = 0; i < list.length; i++ )
{
if ( new File( list[i], "site.xml" ).exists() )
{
ret.add( list[i].getName() );
}
}
}
return ret;
}
public boolean movePageTo( Page source, String newLoc )
{
Page newParent = getPage( (new File( newLoc )).getParent() );
Page oldParent = source.getParent();
if ( !(oldParent instanceof HierarchicalPage) ||
!(newParent instanceof HierarchicalPage) )
{
return false;
}
String newTitle = (new File( newLoc )).getName();
if ( !newTitle.equals( source.getTitle() ) )
{
source.setTitle( newTitle );
}
return ((HierarchicalPage) oldParent).removeSubPage( source ) &&
((HierarchicalPage) newParent).addSubPage( source );
}
public boolean equals( Site site )
{
return site.getId().equals( getId() );
}
public RemoteDocument getVisitorsFile()
{
return RemoteDocument.getDoc( this, "/htpasswd", false );
}
private void loadVisitors()
{
List ret = new LinkedList();
File file = getVisitorsFile();
if ( file.exists() )
{
BufferedReader in = null;
try
{
in = new BufferedReader( new FileReader( file ) );
String line;
while ( (line = in.readLine()) != null )
{
String username = line.substring( 0, line.indexOf( ':' ) );
if ( RemoteDocument.getDoc( this, "/members/" + username, true ).exists() )
{
// this is a full account
continue;
}
ret.add( new Visitor( username ) );
}
}
catch ( IOException e )
{
e.printStackTrace();
}
finally
{
if ( in != null )
{
try
{
in.close();
}
catch ( IOException e )
{
// ignore
}
}
}
}
visitors = ret;
}
public List getVisitors()
{
if ( visitors == null )
{
loadVisitors();
}
return visitors;
}
public Visitor getVisitor( String username )
{
Iterator iter = getVisitors().iterator();
while ( iter.hasNext() )
{
Visitor next = (Visitor) iter.next();
if ( next.getUsername().equals( username ) )
{
return next;
}
}
return null;
}
public void setVisitor( Visitor visitor )
{
String create = "";
if ( getVisitors().size() == 0 )
{
create = "c";
}
Process p = null;
try
{
p = Runtime.getRuntime().exec( "htpasswd -mb" + create + " " + getVisitorsFile().getPath() + " " +
visitor.getUsername() + " " + visitor.getPassword() );
p.waitFor();
}
catch ( IOException e )
{
e.printStackTrace();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
finally
{
if ( p != null )
{
try
{
p.getInputStream().close();
}
catch ( Exception e )
{
}
try
{
p.getErrorStream().close();
}
catch ( Exception e )
{
}
try
{
p.getOutputStream().close();
}
catch ( Exception e )
{
}
}
}
loadVisitors();
}
public void removeVisitor( Visitor visitor )
{
Process p = null;
try
{
p = Runtime.getRuntime().exec( "htpasswd -D " + getVisitorsFile().getPath() + " " + visitor.getUsername() );
p.waitFor();
}
catch ( IOException e )
{
e.printStackTrace();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
finally
{
if ( p != null )
{
try
{
p.getInputStream().close();
}
catch ( Exception e )
{
}
try
{
p.getErrorStream().close();
}
catch ( Exception e )
{
}
try
{
p.getOutputStream().close();
}
catch ( Exception e )
{
}
}
}
loadVisitors();
}
public long calculateSpaceUsage()
{
String folders = new File( XSM.getConfig().getDataDir(), getId() ).getAbsolutePath();
if ( getType() == Site.LOCAL )
{
folders += " " + getRootDir();
}
if ( getQuotaIncludes() != null )
{
folders += " " + getQuotaIncludes();
}
long used = 0;
Process process = null;
StreamGobbler out, err;
try
{
process = Runtime.getRuntime().exec( "du -cm " + folders );
out = new StreamGobbler( process.getInputStream() );
err = new StreamGobbler( process.getErrorStream() );
out.start();
err.start();
process.waitFor();
// check that our gobblers are finished...
while ( !err.isComplete() || !out.isComplete() )
{
try
{
Thread.sleep( 1000 );
}
catch ( InterruptedException e )
{
// we were just trying to tidy up...
}
}
String quotaLine = out.getLastLine();
if ( quotaLine != null )
{
String[] parts = quotaLine.trim().split( "\t" );
if ( parts.length >= 1 )
{
used = Long.parseLong( parts[0] );
}
}
}
catch ( Exception e )
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
finally
{
try
{
process.getInputStream().close();
}
catch ( Exception e )
{
}
try
{
process.getOutputStream().close();
}
catch ( Exception e )
{
}
try
{
process.getErrorStream().close();
}
catch ( Exception e )
{
}
}
return used;
}
}