package Type::Base; ###### 
# 	$Id: Base.pm,v 1.3 2003/05/09 21:31:45 admin Exp $
#	This file is part of Combo and is licensed under the GPL.
#	Copyright 2001-2002 by the authors.  See README and LICENSE
#	for more details or see http://penguinsong.org/.

### Type::Base is the base class for the MIME or content Type handlers.
#
#	This is the base class, with the default interface and a few
#	utilities, for classes in the Type:: package.  Each Type object
#	object represents the behavior of a particular type of web object,
#	and so corresponds roughly to a MIME type or content type.
#	The package could just have easily have been called Handler or Class.
#	

### Design Note:  This will take a while to get right.


### Design Note:  There are actually three ways a Type object can behave:

# * The Type object is invariant, i.e. a "class" or "handler".  In this case,
#	the only reason for instantiating it is to have a reference to get
#	a method from.  Since all of the information we need is either in the
#	resource itself or in the Context, this will work fine in most cases.
#	This mode is also significantly more efficient:  we pass the Context
#	as an argument to every operation rather than keeping it in an
#	instance variable and doing a getContext call and a hash lookup every
#	time we need it.

# * A Type object is instantiated for each resource, and so represents a
#	particular instance, with the package representing its behavior (as is
#	usual for Perl classes).  This would be useful in cases where we need
#	to make multiple passes over the object, since it could then be pulled
#	in only once.  In most other cases, though, it's redundant.

# **A Type object is nothing but a mixin subclass of Combo; it does nothing
#	but add a few methods.  This is, obviously, not a solution that would
#	work well in a compiled language (though you could do it with a
#	factory), but it works Perl.  (We presumabably still have to decide up
#	front whether we're extending Combo or Context, but that could be done
#	in the BEGIN block and in any case we can push that little problem for
#	now.)

# The first two are somewhat compatible; it's basically up to the class which
#	to provide.  It's moot for CGI's anyway; since they're not persistent
#	it becomes important only for mod_perl.  Most, though, will be
#	behavioral only because it's efficient and simple.

# The third looks promising, but could have problems.  I'm not sure
#	what needs to go into @ISA, and it involves reblessing the
#	Context/Combo very late in the process, after we've figured out what
#	it is.  It fails miserably if we need simultaneous information about
#	two resources, so that's a potential disadvantage as well.

# The current thinking is to use the first option:  a Type is a pure handler.
#	Type::Manager will, eventually, cache them so that we only have to
#	load and instantiate each one once (for each Site::Config -- at that
#	point Manager will have to be instantiated.  At the moment it's just
#	a class, but we'll use Manager->* just in case.

use Context;
use Combo;
use Site::Config;
use Site::Util;

BEGIN {
    use vars qw($VERSION @ISA);
    
    $VERSION=1.00;
    @ISA = qw( );
}



#############################################################################
###
### Constructors:
###

### Type->new($config)
#	Create new Type object for the given site Config.
#
#	Note that a Type depends _only_ on the Site::Config -- it has 
#	nothing to do with the query and user information.  In theory,
#	then, we could keep them around forever.  
#
sub new {
    my ($class, $config) = @_;

    # Cache a the Config and a couple of useful constants.
    my $self = {'_Config'=>$config,
		'_doc_root'=>$config->getDocRoot,
	    };
    $self = bless $self, $class;

    return $self;
}


#############################################################################
###
### Access Functions:
###

### $t->getDocRoot
sub getDocRoot {
    my $self = shift;
    return $self->{'_doc_root'};
}

### $t->getConfig
sub getConfig {
    my $self = shift;
    return $self->{'_Config'};
}



#############################################################################
###
### Information Functions:
###

### $t->getContentType($cxt, $realpath)
#	Returns the content type (MIME type) of the resource.  (We have to 
#	pass the path because sometimes the type can't be determined without
#	looking at the filename -- some Type classes are used for multiple
#	MIME types.
#
#   Note that the default implementation won't work with processed files, 
#	most of which return text/html.  Type:: classes for these will
#	have to override getContentType.

sub getContentType {
    my ($self, $cxt, $realpath) = @_;
    my $f = $self->getFilePath($cxt, $realpath);
    return guessMimeType($f);	# works with url path but not as well.

}

### $t->getFilePath($cxt, $realpath)
#	Returns the real file path
sub getFilePath {
    my ($self, $cxt, $realpath) = @_;

    return $self->getDocRoot . $realpath;
}

### $t->canDetermineSize($cxt)
#	Returns true if the size if the content can be determined in advance.
#	Should return 0 if processing is done on the content.
#
sub canDetermineSize {
    my ($self, $cxt) = @_;
    return 1;
}

### $t->canEdit($cxt)
#	Returns true if we know how to edit a resource of this type.
sub canEdit {
    my ($self, $cxt) = @_;

    return 0;
}

### $t->canCreate($cxt)
#	Returns true if we know how to create a resource of this type by
#	editing a nonexistent file.  Note that it does _not_ imply that
#	it is possible or permissible to create any _particular_ file.
sub canCreate {
    my ($self, $cxt) = @_;

    return 0;
}



#############################################################################
###
### Processing Functions:
###

# Note:	We assume that by the time we get to a processing function, all error
#	and permission checking has already been done by the caller, so
#	that all the Type handler has to do is perform the operation.

# Note:	All processing functions take a (url)path as well as a Context.  In
#	some cases, the path of the resource to be retrieved, queried, or 
#	processed is different from that of the resource that was the subject
#	of the original request.  


### $s = $t->editForm($cxt, $realpath)
#	Returns the contents of a form suitable for editing the resource.  
#	Does _not_ include the <form...> tags because those need to be
#	computed by the caller.  Returns null if the object can't be edited.
sub editForm {
    my ($self, $cxt, $realpath) = @_;
    return "";
}


### $s = $t->preview($cxt, $realpath)
#	prints a preview, given the POST response to an edit form.
sub preview {
    my ($self, $cxt, $realpath) = @_;
}


### $s = $t->previewString($cxt, $realpath)
#	Returns a preview string, given the POST response to an edit form.
sub previewString {
    my ($self, $cxt, $realpath) = @_;
}


### $t->process($cxt, $text)
#	output the result of processing the content of the resource.
#	The default, of course, is simply to copy the file to STDOUT.
sub process {
    my ($self, $cxt, $realpath) = @_;

    if (!open(F, $filePath)) {
	return "Cannot open file '$realpath' for reading.";
    }

    my $chunk = '';
    while(read(F, $chunk, 4096)) {
	print $chunk;
    }
    close(F);
    return '';
}


### $t->processText($cxt, $text)
#	output the result of processing $text as if it had been the
#	content of the resource; typically done for a preview.
sub processText {
    my ($self, $cxt, $text) = @_;
    print $text;
}



#############################################################################
###
### HTTP Request Handlers:
###

### $t->handleGET($cxt, $realpath)
#	Handle a (direct) GET request for the resource.  Note that this is
#	a normal, ordinary GET (of the _processed_ content) rather than an 
#	edit.  This is what's used by view.cgi, for example. 
sub handleGET {
    my ($self, $cxt, $realpath) = @_;

#    $self->handleRetrieval($cxt, $realpath, $1) = @_;
}

sub handleHEAD {
    my ($self, $cxt, $realpath) = @_;
    my $filePath = $self->getDocRoot . $realpath;
    my $type = $self->getContentType($cxt, $realpath);

#    $self->handleRetrieval($cxt, $realpath, $1) = @_;
}



### $t->handleQuery($cxt, $realpath, $queryString)
#	Handle a (direct) GET request with a query string.
sub handleQuery {
    my ($self, $cxt, $realpath) = @_;
}

### $t->handlePOST($cxt, $realpath)
#
sub handlePOST {
    my ($self, $cxt, $realpath) = @_;
}

### $t->handlePUT($cxt, $realpath)
#	Handle a PUT request to the resource. 
sub handlePUT {
    my ($self, $cxt, $realpath) = @_;
}

#############################################################################
###
### Utilities:
###

### $t->handleFileRetrieval($cxt, $realpath, $head)
#
#	Return the headers and content for a GET or HEAD request from a 
#	real file (or something that can be derived from it).
#
sub handleRetrieval {
    my ($self, $cxt, $realpath, $head) = @_;

    my $filePath = $self->getFilePath($cxt, $realpath);
    my $type = $self->getContentType($cxt, $realpath);

    # Stat the file:
    my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, 
	$atime, $mtime, $ctime, $blksiz, $blks) 
	= stat $filePath;

    print "Content-type: $type\n";
    print "Size: $size\n" if $self->canDetermineSize($cxt);

    # === need the rest of the headers, esp date.
    # === handle If-Modified-Since

    print "\n";					# end the headers

    return if $head;

    $self->process($cxt, $realpath);
}


#############################################################################
1;
