package Net::UPnP::Service;

#-----------------------------------------------------------------
# Net::Net::UPnP::Service
#-----------------------------------------------------------------

use strict;
use warnings;

use Net::UPnP;
use Net::UPnP::ActionResponse;
use Net::UPnP::QueryResponse;

use vars qw($_DEVICE $_DEVICE_DESCRIPTION $SERVICETYPE $SERVICEID $SCPDURL $CONTROLURL $EVENTSUBURL);

$_DEVICE = 'device';
$_DEVICE_DESCRIPTION = 'device_description';

$SERVICETYPE = 'serviceType';
$SERVICEID = 'serviceId';
$SCPDURL = 'SCPDURL';
$CONTROLURL = 'controlURL';
$EVENTSUBURL = 'eventSubURL';

#------------------------------
# new
#------------------------------

sub new {
	my($class) = shift;
	my($this) = {
		$Net::UPnP::Service::_DEVICE  => undef,
		$Net::UPnP::Service::_DEVICE_DESCRIPTION => '',
	};
	bless $this, $class;
}

#------------------------------
# device
#------------------------------

sub setdevice() {
	my($this) = shift;
	if (@_) {
		$this->{$Net::UPnP::Service::_DEVICE} = $_[0];
	}
}

sub getdevice() {
	my($this) = shift;
	$this->{$Net::UPnP::Service::_DEVICE};
}

#------------------------------
# device description
#------------------------------

sub setdevicedescription() {
	my($this) = shift;
	$this->{$Net::UPnP::Service::_DEVICE_DESCRIPTION} = $_[0];
 }

sub getdevicedescription() {
	my($this) = shift;
	my %args = (
		name => undef,	
		@_,
	);
	if ($args{name}) {
		unless ($this->{$Net::UPnP::Service::_DEVICE_DESCRIPTION} =~ m/<$args{name}>(.*)<\/$args{name}>/i) {
			return '';
		}
	 	return $1;
	}
	$this->{$Net::UPnP::Service::_DEVICE_DESCRIPTION};
 }

#------------------------------
# getservicetype
#------------------------------

sub getservicetype() {
	my($this) = shift;
	$this->getdevicedescription(name => $Net::UPnP::Service::SERVICETYPE);
 }

#------------------------------
# getserviceid
#------------------------------

sub getserviceid() {
	my($this) = shift;
	$this->getdevicedescription(name => $Net::UPnP::Service::SERVICEID);
 }

#------------------------------
# getscpdurl
#------------------------------

sub getscpdurl() {
	my($this) = shift;
	$this->getdevicedescription(name => $Net::UPnP::Service::SCPDURL);
 }

#------------------------------
# getcontrolurl
#------------------------------

sub getcontrolurl() {
	my($this) = shift;
	$this->getdevicedescription(name => $Net::UPnP::Service::CONTROLURL);
 }

#------------------------------
# geteventsuburl
#------------------------------

sub geteventsuburl() {
	my($this) = shift;
	$this->getdevicedescription(name => $Net::UPnP::Service::EVENTSUBURL);
 }

#------------------------------
# getposturl
#------------------------------

sub getposturl() {
	my($this) = shift;
	my ($ctrl_url) = @_;
	my (
		$dev,
		$location_url,
		$url_base,
	);
	
	$dev = $this->getdevice();
	
	$location_url = $dev->getlocation();
	$url_base = $dev->geturlbase();
	$ctrl_url = $this->getcontrolurl();

	#print "$location_url\n";
	#print "$url_base\n";
	#print "$ctrl_url\n";
		
	unless ($ctrl_url =~ m{http://(.*)}i) {
		if (0 < length($url_base)) {
			# Thanks for Thus0 (2005/01/12)
			if (rindex($url_base, '/') == (length($url_base)-1) && index($ctrl_url, '/') == 0) {
				$ctrl_url = $url_base . substr($ctrl_url, 1);
			} else {
				$ctrl_url = $url_base . $ctrl_url;
			}
		}
		else {
			if ($location_url =~ m{http://([0-9a-z.-]+)(?::(\d+))?/(.*)}i) {
                                my $port = $2 || 80;
				if (defined($3) && 0 < length($3)) {
					$ctrl_url = 'http://' . $1 . ":" . $port . $ctrl_url;
				} else {
					$ctrl_url = 'http://' . $1 . ":" . $port . '/' . $ctrl_url;
				}
			} else {
				$ctrl_url = $location_url .  $ctrl_url;
			}
		}
	}
	
	return $ctrl_url;
}

#------------------------------
# postaction
#------------------------------

sub postaction() {
	my($this) = shift;
	my ($action_name, $action_arg) = @_;
	my (
		$dev,
		$ctrl_url,
		$service_type,
		$soap_action,
		$soap_content,
		$arg_name,
		$arg_value,
		$post_addr,
		$post_port,
		$post_path,
		$http_req,
		$post_res,
		$action_res,
		$key,
	);
	
	$action_res = Net::UPnP::ActionResponse->new();
	
	$dev = $this->getdevice();
	$ctrl_url = $this->getcontrolurl();
	$ctrl_url = $this->getposturl($ctrl_url);
	
	unless ($ctrl_url =~ m{http://([0-9a-z.-]+)(?::(\d+))?/(.*)}i) {
		#print "Invalid URL : $ctrl_url\n";
		$post_res = Net::UPnP::HTTPResponse->new();
		$action_res->sethttpresponse($post_res);
		return $action_res;
	}
	$post_addr = $1;
	$post_port = $2 || 80;
	if (index($3, '/') == 0) {
		$post_path = $3;
	}
	else {
		$post_path = '/' . $3;
	}

	$service_type = $this->getservicetype();
	$soap_action = '"' . $service_type . '#' . $action_name . '"';


$soap_content = <<"SOAP_CONTENT";
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<s:Envelope xmlns:s=\"http:\/\/schemas.xmlsoap.org\/soap\/envelope\/\" s:encodingStyle=\"http:\/\/schemas.xmlsoap.org\/soap\/encoding/\">
\t<s:Body>
\t\t<u:$action_name xmlns:u=\"$service_type\">
SOAP_CONTENT

	if (ref $action_arg) {
		while (($arg_name, $arg_value) = each (%{$action_arg} ) ) {
			if (length($arg_value) <= 0) {
				$soap_content .= "\t\t\t<$arg_name \/>\n";
				next;
			}
			$soap_content .= "\t\t\t<$arg_name>$arg_value<\/$arg_name>\n";
		}
	}

$soap_content .= <<"SOAP_CONTENT";
\t\t</u:$action_name>
\t</s:Body>
</s:Envelope>
SOAP_CONTENT

	$http_req = Net::UPnP::HTTP->new();
	$post_res = $http_req->postsoap($post_addr, $post_port, $post_path, $soap_action, $soap_content);

	$action_res->sethttpresponse($post_res);
	
	return $action_res;
}

#------------------------------
# postcontrol
#------------------------------

sub postcontrol() {
	my($this) = shift;
	my ($action_name, $action_arg) = @_;
	return $this->postaction($action_name, $action_arg);
}

#------------------------------
# postquery
#------------------------------

sub postquery() {
	my($this) = shift;
	my ($var_name) = @_;
	my (
		$dev,
		$ctrl_url,
		$service_type,
		$soap_action,
		$soap_content,
		$post_addr,
		$post_port,
		$post_path,
		$http_req,
		$post_res,
		$query_res,
	);
	
	$query_res = Net::UPnP::QueryResponse->new();
	
	$dev = $this->getdevice();
	$ctrl_url = $this->getcontrolurl();
	$ctrl_url = $this->getposturl($ctrl_url);
	
        print "ctrl_url=$ctrl_url\n" if ($Net::UPnP::DEBUG);
	unless ($ctrl_url =~ m{http://([0-9a-z.-]+)(?::(\d+))?/(.*)}i) {
		$post_res = Net::UPnP::HTTPResponse->new();
		$query_res->sethttpresponse($post_res);
		return $query_res;
	}
	$post_addr = $1;
	$post_port = $2 || 80;
	if (index($3, '/') == 0) {
		$post_path = $3;
	}
	else {
		$post_path = '/' . $3;
	}
	$service_type = $this->getservicetype();
	$soap_action = '"urn:schemas-upnp-org:control-1-0#QueryStateVariable"';


$soap_content = <<"SOAP_CONTENT";
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<s:Envelope xmlns:s=\"http:\/\/schemas.xmlsoap.org\/soap\/envelope\/\" s:encodingStyle=\"http:\/\/schemas.xmlsoap.org\/soap\/encoding/\">
\t<s:Body>
\t\t<u:QueryStateVariable xmlns:u=\"urn:schemas-upnp-org:control-1-0\">
\t\t\t<u:varName>$var_name</u:varName>
\t\t</u:QueryStateVariable>
\t</s:Body>
</s:Envelope>
SOAP_CONTENT

	$http_req = Net::UPnP::HTTP->new();
	$post_res = $http_req->postsoap($post_addr, $post_port, $post_path, $soap_action, $soap_content);

	$query_res->sethttpresponse($post_res);
	
	return $query_res;
}

1;

__END__

=head1 NAME

Net::UPnP::Service - Perl extension for UPnP.

=head1 SYNOPSIS

    use Net::UPnP::ControlPoint;

    my $obj = Net::UPnP::ControlPoint->new();

    @dev_list = $obj->search(st =>'upnp:rootdevice', mx => 3);

    $devNum= 0;
    foreach $dev (@dev_list) {
        $device_type = $dev->getdevicetype();
        if  ($device_type ne 'urn:schemas-upnp-org:device:MediaServer:1') {
            next;
        }
        print "[$devNum] : " . $dev->getfriendlyname() . "\n";
        unless ($dev->getservicebyname('urn:schemas-upnp-org:service:ContentDirectory:1')) {
            next;
        }
        $condir_service = $dev->getservicebyname('urn:schemas-upnp-org:service:ContentDirectory:1');
        unless (defined(condir_service)) {
            next;
        }
        %action_in_arg = (
                'ObjectID' => 0,
                'BrowseFlag' => 'BrowseDirectChildren',
                'Filter' => '*',
                'StartingIndex' => 0,
                'RequestedCount' => 0,
                'SortCriteria' => '',
            );
        $action_res = $condir_service->postcontrol('Browse', \%action_in_arg);
        unless ($action_res->getstatuscode() == 200) {
        	next;
        }
        $actrion_out_arg = $action_res->getargumentlist();
        unless ($actrion_out_arg->{'Result'}) {
            next;
        }
        $result = $actrion_out_arg->{'Result'};
        while ($result =~ m/<dc:title>(.*?)<\/dc:title>/sgi) {
            print "\t$1\n";
        }
        $devNum++;
    }

=head1 DESCRIPTION

The package is used a object of UPnP service.

=head1 METHODS

=over 4

=item B<getdevice> - get the device.

    $description = $service->getdevice();

Get the parent device of the service.

=item B<getdevicedescription> - get the service description of the device description.

    $description = $service->getdevicedescription(
    	                        name => $name # undef
                             );

Get the service description of the device description. 
	
The function returns the all description when the name parameter is not specified, otherwise return a value the specified name.

=item B<getservicetype> - get the service type.

    $service_type = $service->getservicetype();

Get the service type.

=item B<getserviceid> - get the service id.

    $service_id = $service->getserviceid();

Get the service id.

=item B<postaction> - post a action control.

    $action_res = $service->postcontrol($action_name, \%action_arg);

Post a action control to the device, and return L<Net::UPnP::ActionResponse>.

The method was renamed from postcontrol(), but the old name is deprecated.
	
=item B<postquery> - post a query control.

    $query_res = $service->postcontrol($var_name);

Post a query control to the device, and return L<Net::UPnP::QueryResponse>.

=back

=head1 SEE ALSO

L<Net::UPnP::ActionResponse>

=head1 AUTHOR

Satoshi Konno
skonno@cybergarage.org

CyberGarage
http://www.cybergarage.org

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Satoshi Konno

It may be used, redistributed, and/or modified under the terms of BSD License.

=cut
