#!/usr/bin/perl

=head1 NAME

CSSJ::Session - セッション

=head2 概要

CSSJで1回の変換を実行するためのセッションです。

=head2 作者

$Date: 2005/11/07 05:40:47 $ MIYABE Tatsuhiko

=cut
package CSSJ::Session;

require Exporter;
@ISA	= qw(Exporter);

use strict;
use Symbol;
use CSSJ::CTIP;
use CSSJ::Builder;
use CSSJ::ResourceOutputTie;
use CSSJ::MainOutputTie;

=head1 定数

=head2 WARN

エラーレベル定数:警告

=cut
sub WARN {return 1;}

=head2 ERROR

エラーレベル定数:エラー

=cut
sub ERROR {return 2;}

=head2 FATAL

エラーレベル定数:致命的エラー

=cut
sub FATAL {return 3;}

=head2 INFO

エラーレベル定数:情報

=cut
sub INFO {return 4;}

=head1 CSSJ::Session

C<new CSSJ::Session IOHANDLE>

セッションのコンストラクタです。

セッションの作成は通常CSSJ::Driver::create_sessionで行うため、
ユーザーがコンストラクタを直接呼び出す必要はありません。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=back

=cut
sub new ($*) {
  my $class = shift;
  my $fp = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());
  my $self = {
    'state' => 1,
    'OUT' => \*STDOUT,
    'FP' => $fp,
    'errorFunc' => undef,
    'progressFunc' => undef
  };
  
  bless $self, $class;
  return $self;
}

=head1 CSSJ::Session->set_output

C<set_output OUTPUTHANDLE [MIME_TYPE]>

変換結果の出力先を指定します。

format_mainおよびstart_mainの前に呼び出してください。
この関数を呼び出さない場合、出力先はSTDOUTになります。

=head2 引数

=over

=item OUTPUTHANDLE 出力先ハンドル。

=item MIME_TYPE 出力形式。省略時は'application/pdf'。

=back

=cut
sub set_output ($*;$) {
  my $self = shift;
  my $out = shift;
  my $mimeType = shift || 'application/pdf';
  $out = Symbol::qualify_to_ref($out, caller());
  
  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  $self->{OUT} = $out;
  return $self->set_property('output.type', $mimeType);
}

=head1 CSSJ::Session->set_error_func

C<set_error_func FUNCTION>

エラーメッセージ受信のためのコールバック関数を設定します。

format_mainおよびob_start_mainの前に呼び出してください。
コールバック関数の引数は、エラーレベル(int)、メッセージ(string)です。

=head2 引数

=over

=item FUNCTION コールバック関数

=back

=cut
sub set_error_func ($&) {
  my $self = shift;
  my $errorFunc = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  $self->{errorFunc} = $errorFunc;
}

=head1 CSSJ::Session->set_progress_func

C<set_progress_func FUNCTION>

進行状況受信のためのコールバック関数を設定します。

format_mainおよびob_start_mainの前に呼び出してください。
コールバック関数の引数は、読み込み済みバイト数(int)です。

=head2 引数

=over

=item FUNCTION コールバック関数

=back

=cut
sub set_progress_func ($&) {
  my $self = shift;
  my $progressFunc = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content is already sent');
    return undef;
  }
  $self->{progressFunc} = $progressFunc;
}

=head1 CSSJ::Session->set_content_length_func

C<set_content_length_func FUNCTION>

出力結果の長さを得るためのコールバック関数を設定します。

format_mainおよびob_start_mainの前に呼び出してください。
コールバック関数の引数は、出力結果のバイト数(int)です。
この関数は、結果が出力されはじめる直前に呼ばれます。
CGIでContent-Lengthヘッダを出力するために有用です。

=head2 引数

=over

=item FUNCTION コールバック関数

=back

=cut

sub set_content_length_func ($&) {
  my $self = shift;
  my $contentLengthFunc = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content is already sent');
    return undef;
  }
  $self->{contentLengthFunc} = $contentLengthFunc;
}

=head1 CSSJ::Session->set_property

C<set_property NAME VALUE>

プロパティを設定します。

セッションを作成した直後に呼び出してください。
利用可能なプロパティの一覧は「開発者ガイド」を参照してください。

=head2 引数

=over

=item NAME 名前

=item VALUE 値

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub set_property ($$$) {
  my $self = shift;
  my $name = shift;
  my $value = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  return CSSJ::CTIP::req_property($self->{FP}, $name, $value);
}

=head1 CSSJ::Session->include_resource

C<include_resource URI_PATTERN>

アクセス可能なサーバー側リソースを設定します。

format_mainおよびstart_mainの前に呼び出してください。

=head2 引数

=over

=item URI_PATTERN URIパターン

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub include_resource ($$) {
  my $self = shift;
  my $uriPattern = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  return CSSJ::CTIP::req_property($self->{FP}, 'ctip.include', $uriPattern);
}

=head1 CSSJ::Session->exclude_resource

C<exclude_resource URI_PATTERN>

除外するサーバー側リソースを設定します。

format_mainおよびstart_mainの前に呼び出してください。

=head2 引数

=over

=item URI_PATTERN URIパターン

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub exclude_resource ($$) {
  my $self = shift;
  my $uriPattern = shift;
  
  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  return CSSJ::CTIP::req_property($self->{FP}, 'ctip.exclude', $uriPattern);
}

=head1 CSSJ::Session->format_main

C<format_main URI>

サーバー側リソースを変換します。

この関数は1つのセッションにつき1度だけ呼ぶことができます。
その後、対象のセッションに対してclose以外の操作はできません。

=head2 引数

=over

=item URI 変換対象のURI

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub format_main ($$) {
  my $self = shift;
  my $uri = shift;

  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  $self->include_resource($uri) or return undef;
  CSSJ::CTIP::req_property($self->{FP}, 'ctip.main', $uri) or return undef;;
  $self->{state} = 2;
  CSSJ::CTIP::req_end($self->{FP}) or return undef;
  defined(CSSJ::Builder::build($self->{FP}, $self->{OUT},
    $self->{errorFunc}, $self->{progressFunc}, $self->{contentLengthFunc})) or return undef;
  return 1;
}

=head1 CSSJ::Session->start_resource

C<start_resource FILEHANDLE URI [MIME_TYPE ENCODING　IGNORE_HEADER]>

クライアント側リソースの送信を開始します。

start_resource,end_resourceは対となります。
これらの関数はformat_mainおよびstart_mainの前に呼び出してください。

指定されたファイルハンドルに書き出されたデータがサーバーに送られます。
ファイルハンドルは新しく作成したものでも、既存のものでも構いません。
例えば、STDOUTを設定すれば、標準出力に書き出したデータがサーバーに送られます。
end_resourceを呼び出すと、ファイルハンドルの状態は元に戻ります。

IGNORE_HEADERに1を設定すると、出力される内容のヘッダ部分を無視します。
データの先頭から、空行までの間がヘッダと認識されます。

=head2 引数

=over

=item FILEHANDLE ファイルハンドル

=item URI リソースの仮想URI

=item MIME_TYPE リソースのタイプ(省略時は'text/css')

=item ENCODING リソースのキャラクタ・エンコーディング

=item IGNORE_HEADER ヘッダを無視する

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub start_resource ($*$;$$$) {
  my $self = shift;
  my $fp = shift;
  my $uri = shift;
  my $mimeType = shift || 'text/css';
  my $encoding = shift || '';
  my $ignoreHeader = shift || 0;
  $fp = Symbol::qualify_to_ref($fp, caller());

  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  if ($ignoreHeader) {
  	$ignoreHeader = 1;
  }
  CSSJ::CTIP::req_resource($self->{FP}, $uri, $mimeType, $encoding) or return undef;
  tie(*$fp, 'CSSJ::ResourceOutputTie', $self->{FP}, $ignoreHeader);
  return 1;
}

=head1 CSSJ::Session->end_resource

C<end_resource FILEHANDLE>

リソースの送信を終了し、ファイルハンドルの状態を復帰します。

start_resource,end_resourceは対となります。
これらの関数はformat_mainおよびstart_mainの前に呼び出してください。

=head2 引数

=over

=item FILEHANDLE ファイルハンドル

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub end_resource ($*) {
  my $self = shift;
  my $fp = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());
  
  close($fp);
  untie(*$fp);
  return 1;
}

=head1 CSSJ::Session->start_main

C<start_main FILEHANDLE URI [MIME_TYPE ENCODING　IGNORE_HEADER]>

クライアント側の本体の送信を開始します。

start_main,end_mainは対となります。
本体の送信は1つのセッションにつき1度だけです。
その後、対象のセッションに対してclose以外の操作はできません。

指定されたファイルハンドルに書き出されたデータがサーバーに送られます。
ファイルハンドルは新しく作成したものでも、既存のものでも構いません。
例えば、STDOUTを設定すれば、標準出力に書き出したデータがサーバーに送られます。
end_mainを呼び出すと、ファイルハンドルの状態は元に戻ります。

IGNORE_HEADERに1を設定すると、出力される内容のヘッダ部分を無視します。
データの先頭から、空行までの間がヘッダと認識されます。

=head2 引数

=over

=item FILEHANDLE ファイルハンドル

=item URI リソースの仮想URI

=item MIME_TYPE リソースのタイプ(省略時は'text/html')

=item ENCODING リソースのキャラクタ・エンコーディング

=item IGNORE_HEADER ヘッダを無視する

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub start_main ($*$;$$) {
  my $self = shift;
  my $fp = shift;
  my $uri = shift;
  my $mimeType = shift || 'text/css';
  my $encoding = shift || '';
  my $ignoreHeader = shift || 0;
  $fp = Symbol::qualify_to_ref($fp, caller());

  if ($self->{state} >= 2) {
    warn ('Main content was already sent');
    return undef;
  }
  if ($ignoreHeader) {
  	$ignoreHeader = 1;
  }
  CSSJ::CTIP::req_main($self->{FP}, $uri, $mimeType, $encoding) or return undef;
  tie(*$fp, 'CSSJ::MainOutputTie', $self->{FP}, $self->{OUT},
    $self->{errorFunc}, $self->{progressFunc}, $self->{contentLengthFunc},
    $ignoreHeader);
}

=head1 CSSJ::Session->end_main

C<end_main FILEHANDLE>

本体の送信を終了し、ファイルハンドルの状態を復帰します。

start_main,end_mainは対となります。
本体の送信は1つのセッションにつき1度だけです。
その後、対象のセッションに対してclose以外の操作はできません。

=head2 引数

=over

=item FILEHANDLE ファイルハンドル

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub end_main ($*) {
  my $self = shift;
  my $fp = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());

  my $builder = close($fp);
  untie(*$fp);
  my $err;
  while ($err = $builder->next()) {};
  defined($err) or return undef;
  return 1;
}

=head1 CSSJ::Session->close

C<close>

セッションを閉じます。

この関数の呼出し後、対象となったセッションに対するいかなる操作もできません。

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub close ($) {
  my $self = shift;
  if ($self->{state} >= 3) {
    warn ('The session is already closed');
    return undef;
  }
  $self->{state} = 3;
  return close($self->{FP});
}
