// CvsDirectoryEntity.java
// $Id: CvsDirectoryResource.java,v 1.37 1997/07/23 13:24:47 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.cvs;

import java.io.* ;
import java.util.* ;
import java.net.*;

import w3c.util.*;
import w3c.www.mime.*;
import w3c.www.http.*;
import w3c.jigsaw.http.* ;
import w3c.jigsaw.auth.AuthFilter;
import w3c.jigsaw.resources.*;
import w3c.jigsaw.forms.*;
import w3c.jigsaw.html.* ;
import w3c.cvs.* ;
import w3c.tools.sorter.*;

/**
 * This class implements an HTML interface to the CVS directory package.
 */

//
// FIXME add extra environment parameter to all public methods
// witch run cvs.
//

public class CvsDirectoryResource extends PostableResource {

    protected static HttpCacheControl CACHE_CONTROL_NOCACHE = null;
    protected static HttpTokenList    PRAGMA_NOCACHE       = null;

    static {
	// Pre-compute the no cache directives:
	CACHE_CONTROL_NOCACHE = HttpFactory.makeCacheControl();
	CACHE_CONTROL_NOCACHE.setNoCache();
	// Pre-compute the no cache directives:
	String nocache[] = { "no-cache" };
	PRAGMA_NOCACHE = HttpFactory.makeStringList(nocache);
    }

    /**
     * Have we already computed our CVS environment ?
     */
    private static boolean inited   = false ;
     
    private CvsDirectory        cvs       = null ;
    private CvsHandlerInterface handler   = null ;

    /**
     * Emit an HTML error message.
     * @param request The request that trigered the error.
     * @param msg The error message.
     * @param ex The CvsException that happened while processing the request.
     * @return An HTTP reply.
     */

    protected static Reply error(Request request
				 , String msg
				 , CvsException ex) {
	Reply reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
	HtmlGenerator g = new HtmlGenerator(msg);
	g.append("<h1>", msg, "</h1>");
	g.append("<p>Error details:<p><em>", ex.getMessage(), "</em>");
	g.append("<hr>You may wish to reload that page, to cope with a "
		 + " well known Java bug.");
	reply.setStream(g);
	return reply;
    }

   /**
     * Get a suitable HTTP resource to display the given cvs'ed file.
     * @param name The name of the file.
     * @return An CvsEntryResource, or <strong>null</strong> if none was
     *    found.
     */

    protected HTTPResource getResourceFor(String name) {
	return new CvsEntryResource(this, name) ;
    }
	    
    /**
     * Get the CVS manager associated with this resource, or create it.
     */

    protected synchronized CvsDirectory getCvsManager() {
	if ( cvs == null ) {
	    // Get our associated cvs manager:
	    HTTPResource parent = getParent();
	    if ( ! (parent instanceof DirectoryResource) ) {
		getServer().errlog(this, "not a child of a DirectoryResource");
		throw new RuntimeException("The server is misconfigured.");
	    } 
	    // CVS will only work within a (filesystem) directory resource
	    File d = ((DirectoryResource) parent).getDirectory();
	    try {
		cvs = CvsDirectory.getManager(d, getServer().getProperties());
	    } catch (CvsException ex) {
		String msg = ("unable to create a cvs manager for \""
                             + d.getAbsolutePath()
			     + "\".");
		getServer().errlog(this, msg);
		throw new RuntimeException("CVS failed.");
	    }
	    handler = new CvsDirectoryHandler(cvs);
	}
	// If this didn't make it:
	return cvs;
    }

    /**
     * Perform the given action on the underlying directory as a whole.
     * @param action The action to perform.
     * @param request The request that triggered the action.
     * @param data The decoded form data.
     * @return A suitable HTTP reply.
     */

    protected Reply performDirectoryAction(String action
					   , Request request
					   , URLDecoder data)
	throws HTTPException
    { 
        if ( action.equals("refresh") ) {
	    // Following command will cause a refresh if needed:
	    try {
		getCvsManager().listFiles();
	    } catch (CvsException ex) {
		return error(request, "Error while refreshing directory", ex);
	    }
	} else if ( action.equals("commit") ) {
	    // Commit the whole directory:
	    try {
		String comment = data.getValue("comment");
		String u = (String)request.getState(AuthFilter.STATE_AUTHUSER);
		String env[] = {"USER="+u , "LOGNAME="+u };
		comment = ((comment == null)
			   ? "Changed through Jigsaw."
			   : comment);
		comment = ((u != null) ? ("("+u+") "+comment) : comment);
		getCvsManager().commit(comment,env);
	    } catch (CvsException ex) {
		return error(request, "Error while commiting directory", ex);
	    }
	} else if ( action.equals("update") ) {
	    // Update the whole directory:
	    try {
		getCvsManager().update();
	    } catch (CvsException ex) {
		return error(request, "Error while updating directory", ex);
	    }
	} else {
	    // Unknown directory command:
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
	    error.setContent("Unknwon action \""+action+"\"");
	    throw new HTTPException(error);
	}
	return get(request);
    }

    /**
     * Lookup method for the CVS manager.
     * Lookup for a cvs entry object having the given name, if found, wrap it
     * into a CvsEntryResource object and return it.
     * @param ls The current lookup state.
     * @param lr The (under construction) lookup result.
     * @exception HTTPException If lookup fails.
     */

    public boolean lookup (LookupState ls, LookupResult lr)
	throws HTTPException
    {
	if ( super.lookup(ls, lr) )
	    return true;
	String name = ls.getNextComponent();
	try {
	    if ( getCvsManager().getDirectoryStatus(name) == CVS.DIR_NCO ) {
		// Checkout directory, and relocate:
		getCvsManager().updateDirectory(name);
		Request request = ls.getRequest();
		if ( request != null ) {
		    Reply relocate = request.makeReply(HTTP.MOVED_TEMPORARILY);
		    try {
			URL myloc    = (ls.hasRequest()
					? getURL(ls.getRequest())
					: new URL(getServer().getURL()
						  , getURLPath()));
			URL location = new URL(myloc, name+"/CVS");
			relocate.setLocation(location);
			lr.setReply(relocate);
		    } catch (Exception ex) {
		    }
		}
		lr.setTarget(null);
		return true;
	    } else if ( getCvsManager().status(name) != CVS.FILE_Q ) {
		HTTPResource target = getResourceFor(name);
		lr.setTarget(target);
		return target.lookup(ls, lr);
	    } else {
		lr.setTarget(null);
		return true;
	    }
	} catch (CvsException ex) {
	    throw new HTTPException("status failed in CVS directory.");
	}
    }

    /**
     * Dump one CVS entry into HTML. 
     * The generated HTML is expected to insert itself in a table.
     * @param g The HTML generator to use.
     * @param name The entry to be dumped.
     * @exception CvsException If the CVS access failed to this entry.
     */

    private void dumpFileEntry (HtmlGenerator g, String name)
	throws CvsException
    {
	String eurl   = getURLPath() + "/" + name;
	int    status = getCvsManager().status(name);

	// Entry toggle:
	g.append("<tr><th><input type=\"checkbox\" name=\""
                 + name
		 + "\" value=\"mark\">" );
	// Dump the entry name (link only if checked out):
	if ( status == CVS.FILE_NCO ) 
	    g.append("<th align=left>" + name);
	else 
	    g.append("<th align=left><a href=\""+name+"\">"
                     + name
		     + "</a>");
	// Dump the entry staus:
	g.append ("<th>") ;
	g.append(getCvsManager().statusToString(status));
	// Emit a diff/log hyper-link only if this makes sense (entry is known)
	if ( status != CVS.FILE_Q )
	    g.append ("<th><a href=\""+eurl+"?log\">log</a>") ;
	if ( status == CVS.FILE_M )
	    g.append ("<th><a href=\""+eurl+"?diff\">diff</a>") ;
    }

    /**
     * Dump one CVS Directory entry into HTML. 
     * The produced HTML is expected to insert itself into a table.
     * @param g The HTML generator to use.
     * @param name The name of the directory to dumped.
     * @exception CvsFailure If the CVS access failed to this entry.
     */

    private void dumpDirectoryEntry(HtmlGenerator g, String name)
	throws CvsException
    {
	int    status = getCvsManager().getDirectoryStatus(name);
	String eurl   = name + "/CVS";

	// Dump the toggle:
	g.append("<tr><th><input type=\"checkbox\" name=\"" + name
		 + "\" value=\"mark\">" );
	// Dump the entry name (and link if available):
	if (status == CVS.DIR_NCO)
	    g.append("<th align=left>" + name);
	else 
	    g.append("<th align=left><a href=\""+name+"\">" + name + "</a>");
	// Dump more links:
	if (status != CVS.DIR_Q) {
	    if ( status != CVS.DIR_NCO )
		g.append("<th align=left><a href=\""
			 , eurl
			 , "\">CVS</a>");
	    else
		g.append("<th align=left><a href=\""
                         + getURLPath() + "/" + name
			 + "\">CVS (checkout)</a>");
	} else {
	    g.append("<th aligne=left>");
	}
	// dump one line for the status:
	g.append ("<th>") ;
	g.append(getCvsManager().statusToString(status));
	g.append ("<th>");
    }

   /**
     * Dump the content of the directory as a CVS form.
     * The resulting form allows for trigerring actions on the various files.
     */
    
    public Reply get (Request request) 
	throws HTTPException
    {
	HtmlGenerator g = new HtmlGenerator ("CVS for "
					     + getCvsManager().getDirectory());
	g.addStyle("CAPTION { color : red }");
	g.addStyle("P.ERROR { color : red }");
	g.addLink(new HtmlLink(null, "made", "jigsaw@w3.org"));
	// Dump all file entries:
	Enumeration enum  = null;
	try {
	    enum = getCvsManager().listFiles() ;
	} catch (CvsException ex) {
	    throw new HTTPException(error(request,"unable to list files",ex));
	}
	Vector names = Sorter.sortStringEnumeration(enum);
	if( names.size() > 0 ) {
	    g.append ("<table width=\"100%\"><td align=\"left\">\n[ Up to ");
	    g.append ("<a href=\"./\">Directory</A> ]</TD>");
	    g.append ("<td align=\"right\">[ The Error log is down ]" );
	    g.append (" </td></table>\n");
	    // now generate the form
	    g.append ("<form action=\"", getURLPath(), "\" method=\"post\">");
	    g.append ("<table width=\"100%\">") ;
	    g.append ("<caption>FILES in ", getURLPath(), "</caption>");
	    // Dump entries, sorted:
	    for (int i = 0 ; i < names.size() ; i++) {
		String   name  = (String)   names.elementAt(i);
		try {
		    dumpFileEntry (g, name) ;
		} catch (CvsException ex) {
		    g.append ("<td>" + name + "<strong>CVS Failed</strong>") ;
		}
	    }
	    g.append ("</table><p>\n") ;
	    // Dump the files command area:
	    g.append ("<table align=\"center\" width=\"100%\"><tr>" 
		      + "<td colspan=2 align=\"center\">");
	    g.append ("<p align=\"center\"><strong>Comments for " +
		      "add/commit files<br>");
	    g.append ("<textarea name=\"comment\" rows=\"3\" cols=\"50\">") ;
	    g.append ("</textarea></td></tr>") ;
	    // add proposed actions
	    g.append ("<tr valign=\"top\"><td><strong>Action:</strong><br>\n");
	    g.append("<input type=\"radio\" name=\"action\" value=\"add\">"
		     + "Add <br>");
	    g.append("<input type=\"radio\" name=\"action\" value=\"update\" "
		     + "checked = \"yes\">Update <br>");
	    g.append("<input type=\"radio\" name=\"action\" value=\"commit\">"
		     + "Commit <br>");
	    g.append("</td>\n<td>");
	    // add proposed scopes:
	    g.append("<strong>Perform action on:</strong><br>\n");
	    g.append("<input type=\"radio\" name=\"scope\" value=\"mark\"" 
		     + "checked=\"yes\">Marked files<br>");
	    g.append("<input type=\"radio\" name=\"scope\" "
		     + "value=\"directory\">Directory<br>");
	    // and close the table and the first form
	    g.append ("</td></tr><tr valign=\"top\"><td align=\"center\" " +
		      "colspan=\"2\">");
	    g.append ("<input type=\"submit\" name=\"submit\" " +
		      "value=\" Perform Action \">" );
	    g.append ("</table></form>\n");
	}
	// get a vector of directory entries
	try {
	    enum = getCvsManager().listDirectories();
	} catch (CvsException ex) {
	    throw new HTTPException(error(request
					  , "unable to list directories"
					  , ex));
	}
	Vector dirnames = Sorter.sortStringEnumeration(enum);
	if( dirnames.size() > 0 ) {
	    // the next one is for stephan
	    g.append ("<hr><p>\n");
            //add the DIRECTORY section
	    g.append("<table width=\"100%\"><td align=\"left\">\n[ Up to ");
	    // old version
	    //g.append("<a href=\"./\">" + "FIXME" + "/</A> ]</TD>");
	    // new version
	    g.append("<a href=\"./\">" + getParent().getURLPath() +
		     "</A> ]</TD>");
	    g.append ("<td align=\"right\">[ The Error log is down ]" );
	    g.append (" </td></table>\n");
            // now generate the form
	    g.append("<form action=\"", getURLPath(), "\" method=\"post\">");
	    g.append("<table width=\"100%\">") ;
	    g.append("<caption>SUBDIRECTORIES in ",getURLPath(),"</caption>");
	    // Dump entries, sorted:
	    for (int i = 0 ; i < dirnames.size() ; i++) {
		String name = (String) dirnames.elementAt(i);
		try {
		    dumpDirectoryEntry (g, name) ;
		} catch (CvsException e) {
		    g.append ("<td>" + name + "<strong>CVS Failed</strong>") ;
		}
	    }
	    g.append ("</table><p>\n");
	}
	g.append ("</body>\n</html>");
	g.close() ;
	// Send back the reply:
	Reply reply = request.makeReply(HTTP.OK) ;
	reply.setHeaderValue(reply.H_CACHE_CONTROL, CACHE_CONTROL_NOCACHE);
	reply.setHeaderValue(reply.H_PRAGMA, PRAGMA_NOCACHE);
	reply.setStream(g);
	return reply ;
    }

    /**
     * This is were we handle the big post request.
     */
    
    public Reply handle (Request request, URLDecoder data)
	throws HTTPException
    {
	String action  = data.getValue("action") ;
	String scope   = data.getValue("scope");

	// no action, is a bug in the generated form (see get)
	if ( action == null ) {
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ;
	    error.setContent("No action selected !");
	    throw new HTTPException (error) ;
	}
	// Check action's scope:
	if ( scope.equals("directory") ) {
	    // Apply action to the whole directory:
	    return performDirectoryAction(action, request, data);
	}
	// Get the list of targets to act on:
	Enumeration enum    = null;
	Vector      targets = new Vector() ;
	try {
	    enum = getCvsManager().listFiles() ;
	} catch (CvsException ex) {
	    error(request, "unable to list files", ex);
	}
	while ( enum.hasMoreElements() ) {
	    String name = (String) enum.nextElement();
	    if ( data.getValue (name) != null ) 
		targets.addElement(name) ;
	}
	// Perform that action:
	if( targets.size() > 0 ) {
	    String names[] = new String[targets.size()];
	    targets.copyInto(names);
	    // Perform the comand :
	    String comment = data.getValue("comment") ;
	    if ( comment != null )
		handler.perform (request, action, names, comment);
	    else
		handler.perform (request, action, names);
	}
	return get(request) ;
    }

    /**
     * Get a CVS directory manager for the given directory.
     * @param server The server wanting to create the CVS directory manager.
     * @param url The URL of the directory to examine.
     * @param dir The directory to exmaine.
     */

    public void initialize(Object values[]) {
	super.initialize(values);
	// Initialize our property sheet:
	if ( ! inited ) {
	    synchronized (this.getClass()) {
		httpd s = getServer();
		if ( s != null ) {
		    // Register the CVS property sheet if not done yet:
		    ObservableProperties props = s.getProperties() ;
		    s.registerPropertySet(new CvsProp("cvs", s));
		    inited = true ;
		}
	    }
	}
    }

    public CvsDirectoryResource() {
    }
}


