#!/usr/bin/perl
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Author:       Alexander Zaitsev <shura0@yandex.ru>
#
# Version:      1.1.0   2017-10-14
#
# Photos autodownloader for Wi-Fi cameras.
# Tested with Canon IXUS 240 HS
use strict;
use warnings;
use IO::Socket;
use IO::Socket::Multicast;
use IO::Select;
use IO::Interface::Simple;
use File::HomeDir;
use Config::Simple;
use TryCatch;


# Photos will be downloaded to the folder $MEDIA_DIR/YYYY/MM/DD
# where YYYY - year, ex. (2015)
#       MM - number of month (01-12)
#       DD - Day of the month (01-31)
# YYYY, MM and DD are date of shooting (from camera)
# i.e. /mnt/media/photos/2015/07/24/

#>>>>> DEFAULT CONFIGURATION
my $HTTP_PORT=8201; # usualy do not need to change
my $HOSTNAME="linux"; # how the PC will be shown in camera
my $INTERFACE="eth0";
my $MEDIA_DIR =File::HomeDir->my_pictures;
my $FILENAME_FORMAT='%Y/%m/%d/%f.%C'; #see man gphoto
#<<<<< DEFAULT CONFIGURATION

my $HOME=File::HomeDir->home;
my $CONFIG_FILE="$HOME/.config/camwatcher";
my $CONFIG=new Config::Simple(syntax=>'simple');
if (-e $CONFIG_FILE) {
	$CONFIG->read($CONFIG_FILE);
	my $p;
	$HTTP_PORT=$p if $p=$CONFIG->param("http_port");
	$HOSTNAME=$p if $p=$CONFIG->param("hostname");
	$INTERFACE=$p if $p=$CONFIG->param("interface");
	$MEDIA_DIR=$p if $p=$CONFIG->param("media_dir");
	$FILENAME_FORMAT=$p if $p=$CONFIG->param("filename_format");
}
else
{
	($INTERFACE)=grep ! /lo/,IO::Interface::Simple->interfaces;
	$INTERFACE or die "Network interfaces not found";
	$CONFIG->param("http_port", "$HTTP_PORT" );
	$CONFIG->param("hostname", "$HOSTNAME" );
	$CONFIG->param("interface", "$INTERFACE" );
	$CONFIG->param("media_dir", "$MEDIA_DIR" );
	$CONFIG->param("filename_format", $FILENAME_FORMAT);
	$CONFIG->write($CONFIG_FILE);
}
$MEDIA_DIR=~s/^~/$HOME/;

sub popup
{
	my $text=shift;
	my $dn = eval
	{
		require Desktop::Notify;
		1;
	};
	if($dn)
	{
		my $notify = Desktop::Notify->new();
		my $notification = $notify->create(summary => 'Canon camera watcher',
														body => $text,
														timeout => 1000);
		$notification->show();
		$notification->close();
	}
	else
	{
		`kdialog --passivepopup "$text" 10`;
	}
}

sub htmlGet($)
{
	my $url=shift;
	my $port=$1 if ($url=~/:(\d+)\//);
	my $ip=$1 if($url=~m|http://(.+?)[:/]|);
	my $doc=$1 if($url=~m|http://.+?/(.*)|);
	$port||=80;
	my $headers="GET /$doc HTTP/1.1\r
Cache-Control: no-cache\r
Connection: Keep-Alive\r
Pragma: no-cache\r
Accept: text/xml, application/xml\r
User-Agent: FDSSDP\r
Host: $ip:$port\r\n\r\n";

	my $data;
	try{
		my $get=IO::Socket::INET->new(PeerPort => $port,
												PeerAddr=> $ip,
												Proto => "tcp",
												Blocking => 1) or die "cannot run server: $@";
		if ($get->connected) {
			$get->write($headers);
			$get->read($data,32768);
		}
		close($get);
	}
	catch{
		print $@;
		return {headers => "", data => ""};
	}
	my @d=split "\r\n\r\n",$data;
	return {headers => shift @d, data => join "\r\n\r\n",@d};
}

my $gphoto=`which gphoto2`;
$gphoto or die "Please install 'gphoto2' package";
chomp $gphoto;
-w $MEDIA_DIR or die "Directory $MEDIA_DIR does not exist or not writable";

my $address=IO::Interface::Simple->new($INTERFACE)->address();
my $packet="HTTP/1.1 200 OK\r
CACHE-CONTROL: max-age=1810\r
ST: upnp:rootdevice\r
USN: uuid:4d696e69-444c-164e-9d41-e8de271b3761::upnp:rootdevice\r
OPT:\"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r
EXT:\r
Server: Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0\r
Location: http://$address:$HTTP_PORT/rootDesc.xml\r\n\r\n";

my $header="HTTP/1.1 200 OK\r
Content-Type: text/xml; charset=\"utf-8\"\r
Connection: close\r
Content-Length: %length%\r
Server: Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0\r
Date: Wed, 15 Jul 2015 18:51:35 GMT\r
EXT:\r
\r\n";

print "Listen address: $address ($INTERFACE)\n";

my $xml=<<END
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
 <specVersion>
  <major>1</major>
  <minor>0</minor>
 </specVersion>
 <device>
  <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
  <friendlyName>$HOSTNAME: root</friendlyName>
  <manufacturer>Justin Maggard</manufacturer>
  <manufacturerURL>http://www.netgear.com/</manufacturerURL>
  <modelDescription>MiniDLNA on Linux</modelDescription>
  <modelName>Rosa</modelName>
  <modelNumber>1</modelNumber>
  <modelURL>http://www.netgear.com</modelURL>
  <serialNumber>12345678</serialNumber>
  <UDN>uuid:4d696e69-444c-164e-9d41-e8de271b3761</UDN>
  <dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMS-1.50</dlna:X_DLNADOC>
  <presentationURL>/</presentationURL>
  <iconList>
	<icon>
	<mimetype>image/jpeg</mimetype>
	<width>48</width>
	<height>48</height>
	<depth>24</depth>
	<url>/icons/sm.jpg</url>
	</icon>
	<icon>
	<mimetype>image/jpeg</mimetype>
	<width>120</width>
	<height>120</height>
	<depth>24</depth>
	<url>/icons/lrg.jpg</url>
	</icon>
  </iconList>
  <serviceList>
	<service>
	<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
	<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
	<controlURL>/ctl/ConnectionMgr</controlURL>
	<eventSubURL>/evt/ConnectionMgr</eventSubURL>
	<SCPDURL>/ConnectionMgr.xml</SCPDURL>
	</service>
	<service>
	<serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType>
	<serviceId>urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar</serviceId>
	<controlURL>/ctl/X_MS_MediaReceiverRegistrar</controlURL>
	<eventSubURL>/evt/X_MS_MediaReceiverRegistrar</eventSubURL>
	<SCPDURL>/X_MS_MediaReceiverRegistrar.xml</SCPDURL>
	</service>
  </serviceList>
 </device>
</root>
END
;


my $len=length($xml);
$header=~s/%length%/$len/;
my $http_response=$header.$xml;
my $server=IO::Socket::INET->new(LocalPort => $HTTP_PORT,
											Proto => "tcp", Listen=>1, Blocking => 0) or
											die "cannot run server: $@";
my $ssdp=IO::Socket::Multicast->new(LocalPort => 1900,
												Proto => "udp", Timeout=> 200, ReuseAddr=>1,
												ReusePort=>1, Blocking => 0) or die "$@";
$ssdp->mcast_add("239.255.255.250", $address);
my $select = new IO::Select($server);
$select->add($ssdp);
my $flag=1;
my $can_get_images=0;
while ($flag) {
	my @p=$select->can_read(0.5);
	my $data;
	S:foreach my $h (@p) {
		if ($h==$ssdp)
		{
			$h->recv($data, 2048);
			if($data=~/Host: 239.255.255.250:1900/ &&
				$data=~/MAN: "ssdp:discover"/)
			{
				$can_get_images=0;
				if($data=~/ST: upnp:rootdevice/)
				{
					$h->send($packet);
				}
			} elsif($data=~/CameraDevDesc.xml/) {
				if ($can_get_images && ($data=~/Location: ?(http:\/\/.*)\r\n/i )) {
					my $url=$1;
					my $data;
					$data=htmlGet($url)->{data};
					$data || next S;
					popup "Camera connected";
					my $ip=$1 if($url=~m|http://(.+?)[:/]|);
					chdir $MEDIA_DIR;
					`$gphoto --port ptpip:$ip --quiet --skip-existing --new --get-all-files --filename=$MEDIA_DIR/$FILENAME_FORMAT`;
					my $message="Photos downloading done";
					$? and $message="Photos downloading failed";
					popup $message;
				}
			}
		}
		elsif ($h==$server) {
			my $conn=$server->accept; $select->add($conn);
		} else {
			$h->recv($data, 2048);
			if(!$data) {
				$select->remove($h); close($h);
			}
			if($data=~/rootDesc.xml/) {
				$h->send($http_response);
				$can_get_images=1;
			}
	  }
	}
}
$select->remove($ssdp);
$select->remove($server);
close $ssdp;
close $server;
exit 0;

