package butch;
use strict;
use utf8;
use Encode;
use Encode::Guess qw/ euc-jp shiftjis 7bit-jis utf8 /;
$Encode::Guess::NoUTFAutoGuess = 1; # utf16 utf32 を候補から外す
use lib './';
use DBI;
use Time::Local;
use Digest::MD5;
use crawl_books;
use base qw(butch::sql butch::page butch::misc);
use vars qw( $dbh );

sub new{
	my $invocant = shift;
	my $class = ref($invocant) || $invocant;
	my $args = shift;
	my ( $obj )  = bless {
		root=> './',
		db_path => 'bolivia/db/',
		dbname=>'bookshelf',
		session_dir=>'bolivia/session/',
		session_days=>365,
		tmp_dir=>'bolivia/tmp/',
		index=>'index.pl',
		profile=>'profile.pl',
		site_static=>'bolivia/static/',

		site_images=>'./bolivia/css/images/',
		no_image_book=>{file=>'no_image_book.jpg',width=>60, height=>88},
		page_title=>{
			'index.pl'=>'本棚', 'profile.pl'=>'プロフィール',
		},
		page_limit=>20,
		pager_page=>10,
		memo_max=>3,
		crawl_max_count=>5,
		comment_length=>2048,

		is_public=>1,

		%$args,
		@_
	}, $class;
	if( $dbh eq '' ){
		$obj->connect_db($args->{dbname});
	}
	return $obj;
}
#
sub DESTROY {
	my $self = shift;
	$dbh->disconnect;
	undef $dbh;
}
sub _test{
	my $self = shift;
}
####
sub serial_id{
	my $self = shift;
	my $args = shift;
	my ($serial) = $dbh->selectrow_array(qq{select max($args->{id}) from $args->{table}});
	return $serial+1;
}
sub strtime{
	my $self = shift;
	my $timestamp = (shift || time);
	$timestamp += 9 * 60 * 60;
	my ($current) = $dbh->selectrow_array(qq{select strftime("%Y%m%d%H%M%S", $timestamp,'unixepoch' ) });
	# yyyymmddhhmmss
	return $current;
}
###
sub check_change_db{
	my $self = shift;
	my $uid  = shift;
	my $dbname;
	if( $uid =~ /^\d+$/){
		$dbname = sprintf qq{%05d}, $uid;
	}
	elsif($uid =~ /(butch|sundance)/){
		$dbname = $1;
	}
	else{
		return;
	}
	if( -e $self->{root} . $self->{db_path} . $dbname . '.sqlite' ) {
		$self->disconnect_db;
		$self->connect_db($dbname);
		return $uid;
	}
	return;
}
sub change_db{
	my $self = shift;
	my $uid  = shift;
	return if ! $uid;

	$self->disconnect_db;
	$self->connect_db( sprintf qq{%05d}, $uid );
	return $uid;
}
sub vacuum_db{
	my $self = shift;
	$dbh->do('VACUUM');
	return;
}
sub connect_db{
	my $self = shift;
	my $dbname = (shift || return);

	$dbname = $self->{root} . $self->{db_path} . $dbname . '.sqlite';
	$dbh = DBI->connect( "dbi:SQLite:dbname=$dbname","","",{ RaiseError => 1, AutoCommit=>1 });
	if( ! $dbh ) { die 'cannot connect db'; }
	return $dbh;
}
sub disconnect_db{
	my $self = shift;
	return if ! $dbh;
	$dbh->disconnect;
	undef $dbh;
}
###
sub execute_sql{
	my $self = shift;
	my $sql  = shift;
	return if ! $sql;
	my $sth;
	eval{$sth = $dbh->prepare($sql);};
	if($@){
		printf qq{%s<br />\n%s}, $sql, $@;
		return;
	}
	my $r   = $sth->execute;

	return $r;
}
sub select_data{
	my $self = shift;
	my $sql  = shift;
	my $cols = shift;
	return if ! $sql;
	my $sth;
	eval{$sth = $dbh->prepare($sql);};
	if($@){
		printf qq{%s<br />\n%s}, $sql, $@;
		return;
	}
	my $r   = $sth->execute;
	$cols= $sth->{NAME} if ! $cols;
	my @buf;
	while( my $p = $sth->fetchrow_arrayref){
		my %h;
		map{$_ = $self->touch_decode($_)} @{$p};
		map{$_ =~ s/\'\'/\'/g;} @{$p}; # for sqlite escape
		@h{ @{$cols} } = @{$p};
		push(@buf, \%h);
	}
	return \@buf;
}
### insert book from amazon
sub edit_profile{
	my $self = shift;
	my $uid  = shift;
	my $args = shift;
	return if ! $uid;
	return if ! $args;

	$args->{uid} = $uid;
	my ($checked) = $dbh->selectrow_array(qq{select uid from prof where uid=$uid});
	if( ! $checked ){
		$self->execute_sql($self->insert_prof($args));
	}
	else{
		$self->execute_sql($self->update_prof($args));
	}
}
sub edit_bookstar{
	my $self = shift;
	my $uid  = shift;
	my $args = shift;
	return if ! $uid;
	return if ! $args;

	for(my $i=1; $i<=10; $i++){
		my ($checked) = $dbh->selectrow_array(qq{select uid from bookstar_list where uid=$uid and starid=$i});
		$args->{'starstr' . $i} = substr($args->{'starstr' . $i}, 0,1) if length($args->{'starstr' . $i}) > 1;
		$args->{'starcolor' . $i} =~ s/^#//;
		my $starname = $args->{'starname' . $i} ? $args->{'starname' . $i} : 'カテゴリ' . $i;
		my $starstr  = $args->{'starstr' . $i} ? $args->{'starstr' . $i} : '■';
		my $starcolor = $args->{'starcolor' . $i} ? $args->{'starcolor' . $i} : 'cccccc';
		my $cols = {
			uid=>$uid,
			starid=>$i,
			starname=>$starname,
			staricon=>'',
			starcolor=>$starcolor,
			starstr=>$starstr
		};
		if( ! $checked ){
			$self->execute_sql($self->insert_bookstar($cols));
		}
		else{
			$self->execute_sql($self->update_bookstar($cols));
		}
	}
}
sub import_book{
	my $self = shift;
	my $uid  = shift;
	my $args = shift;

	return if(! $args);

	my $err;
	my $checked;
	# isbn チェック
	$checked = $self->check_isbn($args->{_isbn});
	if( ! $checked ){
		$err->{$args->{_isbn}} = 'wrong'; return $err;
	}
	my $asin = $checked; # check_isbn が amazon の asinを生成して返している。
	# 重複 isbn
	($checked) = $dbh->selectrow_array(qq{select isbn from books_list where isbn='$asin'});
	if( $checked ){
		$err->{$args->{_isbn}} = 'duplicate'; return $err;
	}
	my $crawl = crawl_books->new({});
	my ($book, $pages) = $crawl->check_cache({'isbn'=>$asin});
	if( ! ref($book) ){
		$err->{$args->{_isbn}} = 'nonexistent'; return $err;
	}

	if(! $book->[0]{title}){
		$err->{$args->{_isbn}} = 'not found --- ' . $args->{_isbn}; return $err;
	}

	$book->[0]{uid} = $uid;
	$book->[0]{timestamp} = time;
	$book->[0]{bookid} = $self->serial_id({id=>'bookid',table=>'books_list'});
	$book->[0]{ymd} = $self->strtime($book->[0]{timestamp});
	$book->[0]{readed} = $args->{_readed} ? 1 : 0;
	$book->[0]{bookstar} = sprintf qq{%d}, $args->{_bookstar};
	$self->execute_sql( $self->insert_book($book) );
	foreach ( @{$book->[0]{creator}} ){
		$book->[0]{authorid} = $self->serial_id({id=>'authorid',table=>'authors_list'});
		$book->[0]{author} = $_;
		$self->execute_sql( $self->insert_author($book) );
	}
	return 1;
}
### prof
sub get_prof{
	my $self = shift;
	return $self->select_data(qq{select * from prof});
}
sub get_profile_summary{
	my $self = shift;
	my $args = shift;

	my $prof   = $self->get_prof;
	my $stars  = $self->select_data( $self->get_bookstar_list );
	my $readed = $self->select_data(qq{select readed, count(*) as cnt from books_list group by readed order by readed});
	my $bookstar= $self->select_data(qq{select bookstar, count(*) as cnt from books_list group by bookstar order by bookstar});
	my $authors= $self->select_data(qq{select author, count(*) as cnt from authors_list group by author order by cnt desc limit 10 offset 0});


	return {profile=>$prof,stars=>$stars,readed=>$readed,bookstar=>$bookstar,authors=>$authors};
}
# books
sub get_memo{
	my $self = shift;
	my $args = shift;
	return if ! $args;
	return if ! $args->{commentid};
	return $self->select_data(qq{select * from books_memo where commentid=$args->{commentid}});
}
sub insert_memo{
	my $self = shift;
	my $args = shift;
	return if ! $args;
	return if length($args->{comment}) > $self->{comment_length};

	my ($cnt) = $dbh->selectrow_array(qq{select count(uid) from books_memo where uid=$args->{uid} and bookid=$args->{bookid}});
	return if $cnt >= $self->{memo_max};

	my $commentid = $self->serial_id({id=>'commentid',table=>'books_memo'});
	my $timestamp = time;
	my $netabare = $args->{netabare} ? 1 : 0;
	$self->execute_sql( $self->insert_bookmemo({uid=>$args->{uid}, bookid=>$args->{bookid}, commentid=>$commentid, comment=>$args->{comment}, netabare=>$netabare,ctime=>$timestamp,mtime=>$timestamp}) );
	$self->execute_sql( $self->update_book_timestamp({uid=>$args->{uid}, bookid=>$args->{bookid}}) );
	return $commentid;
}
sub update_memo{
	my $self = shift;
	my $args = shift;
	return if ! $args;
	return if length($args->{comment}) > $self->{comment_length};

	my $timestamp = time;
	my $netabare = $args->{netabare} ? 1 : 0;
	$self->execute_sql( $self->update_bookmemo({commentid=>$args->{commentid}, comment=>$args->{comment}, netabare=>$netabare, mtime=>$timestamp}) );
}
sub delete_memo{
	my $self = shift;
	my $args = shift;
	my ( $bookid ) = $dbh->selectrow_array(qq{select bookid from books_memo where commentid=$args->{commentid}});
	my $sql = qq{delete from books_memo where commentid=$args->{commentid}};
	$self->execute_sql($sql);
	return $bookid;
}
#
sub list_books{
	my $self = shift;
	my $args  = shift;
	return if ! $args;

	my ($cnt, $min, $max) = $dbh->selectrow_array(qq{select count(uid), min(mtime), max(mtime) from books_list});
	$args->{allcount} = $cnt;

	my $books;
	if($args && ($args->{uid}=~/^\d+$/ || $args->{bs}=~/^\d+$/) && $args->{bookid}=~/^\d+$/ && $args->{_} =~ /^\d+$/){
		# from ajax js_view_book.pl and visiter.pl
		my $uid = ($args->{bs} || $args->{uid});
		$books = $self->select_data( $self->get_book({uid=>$uid, bookid=>$args->{bookid}}) );
		if( $books->[0]{isbn} ){
			my $crawl = crawl_books->new({});
			my ($ref, $pages) = $crawl->check_cache({'isbn'=>$books->[0]{isbn}});
			if( ref($ref) ){
				#$books->[0]{image}->{url} = $ref->[0]{simage};
				$books->[0]{image}->{url} = $ref->[0]{simage};
				#$books->[0]{image}->{height} = $ref->[0]{simage_h};
				#$books->[0]{image}->{width}  = $ref->[0]{simage_w};
				$books->[0]{image}->{width}  = 60;
			}
		}
	}
	else{
		if($args->{_year}){
			$args->{ymd} = $args->{_year} . $args->{_month};
		}
		my $bookids; my $target_bookid;
		if( $args->{search} ne '' ){
			if( $args->{col} eq 'author'){
				$bookids = $self->select_data($self->get_bookid_from_authors($args));
			}
			elsif( $args->{col} eq 'memo'){
				$bookids = $self->select_data($self->get_bookid_from_memos($args));
			}
		}
		elsif( $args->{author} ne '' ){
			$bookids = $self->select_data($self->get_bookid_from_authors($args));
		}
		if(ref($bookids)){
			my @buf; foreach (@{$bookids}){ push(@buf, $_->{bookid}); }
			$target_bookid = join(',', @buf);
		}
		my $ref = $self->get_books_list($args, $target_bookid);
		# get count
		$args->{maxcount} = $dbh->selectrow_array($ref->{count});
		# get list
		$books = $self->select_data( $ref->{sql} );
	}
	my $authors;
	foreach (@{$books}){
		$authors->{$_->{uid}}->{$_->{bookid}} = $self->select_data( $self->get_authors({uid=>$_->{uid}, bookid=>$_->{bookid}}) );
	}
	my $has_memo;
	foreach (@{$books}){
		$has_memo->{$_->{uid}}->{$_->{bookid}} = $self->select_data( $self->get_memos({uid=>$_->{uid}, bookid=>$_->{bookid}}) );
	}
	my $stars;
	foreach (@{ $self->select_data( $self->get_bookstar_list ) } ){
		$stars->{$_->{starid}}->{name}  = $_->{starname};
		$stars->{$_->{starid}}->{icon}  = $_->{staricon};
		$stars->{$_->{starid}}->{color} = $_->{starcolor} ? $_->{starcolor} : 'cccccc';
		$stars->{$_->{starid}}->{str}   = $_->{starstr};
	}
	return {allcount=>$cnt, maxcount=>$args->{maxcount}, begin=>$min, latest=>$max, books=>$books, authors=>$authors, has_memo=>$has_memo, stars=>$stars};
}
#
sub update_bookinfo{
	my $self = shift;
	my $args = shift;
	return if ! $args;
	return unless( $args->{uid} && $args->{bookid});
	$self->execute_sql($self->update_book($args));
}
#
sub delete_bookinfo{
	my $self = shift;
	my $args = shift;
	return if ! $args;
	return unless( $args->{uid} && $args->{bookid});

	my $sql = qq{delete from books_memo where uid=$args->{uid} and bookid=$args->{bookid}};
	$self->execute_sql($sql);
	$sql = qq{delete from authors_list where uid=$args->{uid} and bookid=$args->{bookid}};
	$self->execute_sql($sql);
	$sql = qq{delete from books_list where uid=$args->{uid} and bookid=$args->{bookid}};
	$self->execute_sql($sql);
	1;
}
### from siteutil
# multipart の入力を解析
sub read_form_multipart{
	my $self  = shift;
	my $limit = shift;
	my $opt   = shift;
	$self->err_msg(0, 'File size is too large.') if $limit && $ENV{'CONTENT_LENGTH'} > $limit;

	my %form; my %form_file; my %zzz;
	my $buf;
	read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});

	# 2006-07-07 boundary $ENV{'CONTENT_TYPE'};
	my($boundary) = $ENV{'CONTENT_TYPE'} =~ /boundary=-(\S+)$/;	# バウンダリの取得
	my @form_body = split(/\-*${boundary}\-*/, $buf);
	foreach my $c ( @form_body ){
		next if ! $c;

		my ($disposition,$header,$body);
		if($c =~ /[Cc]ontent-[Dd]isposition: ([^\x0D\x0A]+)\x0D?\x0A	# Disposition
                    (?:([A-Za-z].*?)(?:\x0D?\x0A){2})?						# ヘッダ情報の取得
                    (?:\x0D?\x0A)?										# 予約空行
                    (?=(.+))/xs){										# データ行(次のヘッダ情報が含まれていることもある)
			($disposition,$header,$body) = ($1,$2,$3);
		}
		next if ! $disposition;

		# if( $disposition =~ /filename=\"([^\"]+)\"/i ){ 2011-12-04 13:34:12
		if( $disposition =~ /name=\"([^\"]+)\"; *filename=\"([^\"]+)\"/i ){
			my $name = $1;
			my $filename = $2; $filename =~ s!\\!/!g; $filename =~ s!^.+/!!;
			$form_file{$filename} = $body if $filename && $body;
			$form{$name} = $filename;
			$name=""; $filename =""; $body = "";
		}
		else{
			my ( $name ) = $disposition =~ /.+name=\"([^\"]+)\"/i;
			next if ! $name;
			$body =~ s/(\r\n)+$//;

			my $enc;
			if( $opt->{enc} ){
				$enc = $opt->{enc};
			}
			else{
				my $guess = Encode::Guess->guess($body);
				$enc = ref($guess) ? $guess->name : 'utf8';
			}
			$body = Encode::encode('utf8',Encode::decode($enc, $body));

			$body = Encode::decode('utf8', $body) if ! Encode::is_utf8($body);
			$name = Encode::decode('utf8', $name) if ! Encode::is_utf8($name);

			unless ( $body =~ /^--+/ ){
				$body =~ s/\x0D\x0A/\n/g;
				$body =~ s/\x0D/\n/g;
				$body =~ s/\,/,/g;

				if( ! $zzz{$name}++ ){
					$form{$name} = $body;
				}
				else{
					$form{$name} .= "," . $body;
				}
				$name = ""; $body = "";
			}
		}
	}
	return (\%form, \%form_file) if $buf;
}
sub read_form{
	my $self  = shift;
	my $limit = shift;
	my $opt   = shift;
	my($buf,$form_name, $form_value, $pair,@pairs);
	my %form;
	my %zzz;
	if( $ENV{'REQUEST_METHOD'} eq 'POST' ){
		read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});
	}
	else{
		$buf = $ENV{'QUERY_STRING'};
	}
	$self->err_msg(0, 'File size is too large.') if $limit && $ENV{'CONTENT_LENGTH'} > $limit;
	@pairs = split(/&/,$buf);
	foreach $pair (@pairs){
		($form_name, $form_value) = split(/=/,$pair);
		$form_value =~ tr/+/ /;
		$form_value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;

		my $enc;
		if( $opt->{enc} ){
			$enc = $opt->{enc};
		}
		else{
			my $guess = Encode::Guess->guess($form_value);
			$enc = ref($guess) ? $guess->name : 'utf8';
		}
		$form_value = Encode::encode('utf8',Encode::decode($enc, $form_value));

		$form_name  = Encode::decode('utf8', $form_name)  if ! Encode::is_utf8($form_name);
		$form_value = Encode::decode('utf8', $form_value) if ! Encode::is_utf8($form_value);

		$form_value =~ s/\x0D\x0A/\n/g;
		$form_value =~ s/\x0D/\n/g;
		$form_value =~ s/\</</g;
		$form_value =~ s/\>/>/g;
		$form_value =~ s/\,/,/g;


		if( ! $zzz{$form_name}++ ){
			$form{$form_name} = $form_value;
		}
		else{
			$form{$form_name} .= "," . $form_value;
		}
	}
	return \%form;
}
#
sub err_msg{
	my $self = shift;
	my $pos  = shift;
	my $msg  = shift;
	if( $pos < 1){
		print "Content-type:text/html\n\n";
		print "<html><body>";
	}
	print '<p class="err_msg">';
	print $msg;
	print '</p>';
	if( $pos < 1){
		print "</body></html>";
	}
	exit;
}
sub touch_decode{
	my $self = shift;
	my $c    = shift;
	$c = Encode::decode('utf8', $c) if ! Encode::is_utf8($c);
	return $c;
}
sub touch_encode{
	my $self = shift;
	my $c    = shift;
	$c = Encode::encode('utf8', $c) if Encode::is_utf8($c);
	return $c;
}
sub touch_encode2shiftjis{
	my $self = shift;
	my $c    = shift;
	$c = Encode::encode('shiftjis', $c) if Encode::is_utf8($c);
	return $c;
}
sub html_encode{
	my $self = shift;
	my $str = shift;
	my $enc = (shift || 'utf8');
	$str = Encode::encode($enc, $str) if Encode::is_utf8( $str );
	$str =~ s/([^0-9a-zA-Z~._-])/sprintf("%%%02X",unpack("C", $1))/eg;
	return $str;
}
sub html_decode{
	my $self = shift;
	my $str  = shift;
	$str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
	return $str;
}
sub login_form{
	my $self = shift;
	my $form = shift;
	printf qq{<form action="%s" method="post">}, $self->{script};
	print qq{<h2>ログイン</h2>};

	print '<div class="content">';
	print qq{<dl id="login_form" class="inform">};
	print qq{<dt>メールアドレス:</dt>};
	printf qq{<dd><input type="text" name="email" value="%s" size="30" maxlength="128" /></dd>}, $form && $form->{email} ? $form->{email} :'';
	print qq{<dt>パスワード:</dt>};
	print qq{<dd><input type="password" name="pass" value="" size="30" maxlength="128" /></dd>};
	print qq{<dd class="submit_btn"><input type="submit" name="submit" value="送信" /></dd>};
	print qq{</dl>};
	print qq{<p>(メールアドレスとありますが、アルファベットなど半角でしたら何でもokです)</p>};
	print qq{<p>(ログイン リセットの場合もこの画面が表示されます)</p>};
	if( $form && $form->{email} ){
		$self->msg_err('メールアドレス、パスワードをご確認ください。');
	}
	print '</div>';
	print qq{</form>};
}
#
sub check_login{
	my $self = shift;
	my $args = shift;
	
	my $cookie = $self->get_cookie(['_bookshelf']);
	if( $args->{op} eq 'reset_login' ){ # for reset 
		my $sql = qq{delete from members where email is not null};
		$self->execute_sql($sql);
		unlink $self->{root} . $self->{session_dir} . $cookie->{'_bookshelf'};
		printf qq{Set-Cookie: _bookshelf=; expires=%s;\n}, $self->cookie_expires(-1);
		$self->go_entrance;
		return;
	}

	my $first_setting;
	eval{ $self->selectrow_array(qq{select * from members});};
	if($@){
		$self->execute_sql($self->make_table_members());
		$first_setting = 1;
	}
	if( $first_setting ){
		eval{ $self->selectrow_array(qq{select * from prof});};
		if($@){
			$self->setting_bookshelf();
		}
	}

	my $alive = $self->{session_days};
	# check session
	my $now = time;
	opendir(DIR, $self->{root} . $self->{session_dir}) || die; my @w = grep(/^[a-z0-9]+$/, readdir(DIR)); closedir(DIR);
	foreach( @w ){
		my $mtime = (stat($self->{root} . $self->{session_dir} . $_))[9];
		if( ($now - $mtime) > 60*60*24* $alive ){
			unlink $self->{root} . $self->{session_dir} . $_;
		}
	}

	if( $args->{email} && $args->{pass} ){
		return if( $args->{email} =~ m!\'! || $args->{pass} =~ m!\'! );
		my ($pass) = $dbh->selectrow_array(qq{select pass from members where email='$args->{email}'});
		my ($count) = $dbh->selectrow_array(qq{select count(*) from members});

		if( ! $pass && ! $count ){
			my $timestamp = time;
			my $pass = $self->make_pass({pass=>$args->{pass}});
			my $sql = qq{insert into members (email, pass, mtime, enabled) values ('$args->{email}', '$pass', $timestamp, 1)};
			$self->execute_sql($sql);
			my $digest = $self->set_session({email=>$args->{email}});
			printf qq{Set-Cookie: _bookshelf=%s; expires=%s;\n}, $digest, $self->cookie_expires($alive);
			return sprintf qq{Set-Cookie: _bookshelf=%s; expires=%s;\n}, $digest, $self->cookie_expires($alive);
		}
		if($pass){
			if( $self->check_pass({input=>$args->{pass}, pass=>$pass}) ){
				my $digest = $self->set_session({email=>$args->{email}});
				if($digest){
					$self->set_session({email=>$args->{email}});
					printf qq{Set-Cookie: _bookshelf=%s; expires=%s;\n}, $digest, $self->cookie_expires($alive);
					return sprintf qq{Set-Cookie: _bookshelf=%s; expires=%s;\n}, $digest, $self->cookie_expires($alive);
				}
			}
		}
	}
	elsif( $cookie->{'_bookshelf'} ){
		if( -f $self->{root} . $self->{session_dir} . $cookie->{'_bookshelf'} ){
			if( $args->{op} eq 'logout' ){ # for logout
				unlink $self->{root} . $self->{session_dir} . $cookie->{'_bookshelf'};
				printf qq{Set-Cookie: _bookshelf=; expires=%s;\n}, $self->cookie_expires(-1);
				return;
			}
			return $cookie->{'_bookshelf'};
		}
	}
	printf qq{Set-Cookie: _bookshelf=; expires=%s;\n}, $self->cookie_expires(-1);
	return;
}
#
sub set_session{
	my $self = shift;
	my $args = shift;
	my $timestamp = time;

	my $md5 = Digest::MD5->new;
	$md5->add(time, $args->{email});
	my $digest = $md5->hexdigest;
	my $session_file = $self->{root} . $self->{session_dir} . $digest;
	eval {
		no strict 'refs';
		my $fh = 'FH000';
		++$fh while fileno($fh);
		open($fh, '>' . $session_file) || die; print $fh $timestamp; close($fh);
	};
	if( $@ ){ return; }else{ return $digest; }
}
sub make_pass{
	my $self = shift;
	my $args = shift;
	return if ! $args->{pass};
	return $self->mk_crypt($args->{pass});
}
sub mk_crypt{
	my $self  = shift;
	my $input = shift;
	srand();
	my $salt = time;
	$salt = substr($salt, int(rand(length($salt))), 1);
	my $alph = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$salt .= substr($alph, int(rand(length($alph))), 1);
	return crypt($input, $salt);
}
#
sub check_pass{
	my $self = shift;
	my $args = shift;
	my $salt = substr($args->{pass}, 0, 2);
	return ( crypt($args->{input},$salt) eq $args->{pass} ) ? 1 : 0;
}
sub get_cookie{
	my $self   = shift;
	my $target = shift;

	my $pickup;
	if( ref($target) ){
		foreach( @{ $target } ){ $pickup->{$_}++; }
	}
	elsif( $target ne '' ){
		$pickup->{$target} = 1;
	}
	my %cookie;
	foreach my $pair ( split(/;\s*/,$ENV{'HTTP_COOKIE'}) ){
		my ($key, $value) = split(/=/, $pair); 
		next if( $pickup && ! $pickup->{$key} );
		$value =~ tr/+/ /;
		$value =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex($1))/eg;
		$value = Encode::decode('utf8',$value) if ! Encode::is_utf8($value);
		$cookie{ $key } = $value;
	}
	return \%cookie;
}
sub set_cookie{
	my $self  = shift;
	my $pairs = shift;

	foreach ( keys %{ $pairs } ){
		next if ! $pairs->{$_};
		printf"Set-Cookie: %s=%s; expires=%s;%s\n", $_, $self->html_encode($pairs->{$_}), $self->cookie_expires(30),
		$ENV{'HTTP_USER_AGENT'} =~ /UP\.Browser/i ? ' Max-Age=' . 60*60*24*30 .';' : '';
	}
	1;
}
sub cookie_expires {
	my $self = shift;
	my $days = shift;

	my $expires;
	my $t = time;
	$days = 1 if ! $days;		# デフォルトはとりあえず1日
	if($days == -1){
		$expires = 'Thu, 1-Jan-1980 00:00:00 GMT';
	}
	else{
		$days = $days * 60 * 60 * 24;
		my ($s,$mi,$h,$d,$m,$y,$wd) = (localtime(($t+$days)))[0..6]; ++$m; $y+=1900;
		$expires = sprintf("%s, %02d-%s-%s %02d:%02d:%02d GMT",
					   ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$wd],
						   $d,
						   ('','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$m],
						   $y,
						   $h,$mi,$s);
	}
	return $expires;
}
sub clear_cookie{
	my $self = shift;
	my $key  = shift;
	foreach ( @{$key} ){
		printf qq{Set-Cookie: %s=''; expires=Thu, 01-Jan-1970 00:00:00 GMT;\n}, $_;
	}
	1;
}
sub setting_bookshelf{
	my $self = shift;

	$self->execute_sql( $self->make_books_list );
	$self->execute_sql( $self->make_authors_list );
	$self->execute_sql( $self->make_books_memo );
	$self->execute_sql( $self->make_bookstar_list );
	$self->execute_sql( $self->make_prof );

	$self->set_default_bookstars({uid=>1});
}
sub set_default_bookstars{
	my $self = shift;
	my $args = shift;

	my $uid = $args->{uid};
	my $stars = $self->select_data(qq{select * from bookstar_list});
	return if @{$stars} >= 10;
	my $timestamp = time;
	foreach (0..10){
		next if $stars->[$_]{starid};
		my $cat = ! $_ ? '未分類' : 'カテゴリ' . $_;
		my $sql = qq{
insert into bookstar_list (uid, starid,starname,staricon,starcolor,starstr, ctime,mtime)
  values ($uid, $_, '$cat','','999999','■', $timestamp,$timestamp)
};
		$self->execute_sql($sql);
	}
}

sub go_entrance{
	my $self = shift;
	printf qq{Location:%s\n\n}, $self->{index};
	exit;
}

### backup
sub download_backup{
	my $self = shift;
	my $uid  = shift;
	return if ! $uid;

	$self->check_change_db($uid);

	my $sql = qq{select * from bookstar_list where bookstar_list.uid=$uid order by bookstar_list.starid asc};
	my $stars = $self->select_data($sql);
	$sql = qq{select * from books_list where books_list.uid=$uid order by books_list.mtime desc};
	my $books = $self->select_data($sql);

	print qq{Content-type: application/octet-stream\n};
	print qq{Content-Disposition: inline; filename=bookshelf.xml\n\n};
	print qq{<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>};
	print qq{<dokusho>\n};
	print qq{<categories>\n};
	foreach (@{$stars}){
		print qq{<category>};
		printf qq{<id>%d</id>}, $_->{starid};
		printf qq{<name><![CDATA[%s]]></name>}, $_->{starname};
		printf qq{<color>%s</color>}, $_->{starcolor};
		printf qq{<mark><![CDATA[%s]]></mark>}, $_->{starstr};
		print qq{</category>\n};
	}
	print qq{</categories>\n};
	print qq{<bookslist>\n};
	foreach (@{$books}){
		print qq{<book>};
		printf qq{<isbn>%s</isbn>},$_->{isbn};
		printf qq{<title><![CDATA[%s]]></title>},$_->{title};
		printf qq{<readed>%s</readed>},$_->{readed};
		printf qq{<category>%d</category>}, $_->{bookstar};
		printf qq{<date>%s</date>}, $_->{ymd};
		my $authors = $self->select_data(qq{select * from authors_list where authors_list.uid=$uid and authors_list.bookid=$_->{bookid} order by authors_list.authorid});
		if( $authors->[0]{uid} ){
			print qq{<authors>};
			foreach ( @{$authors} ){
				printf qq{<author><![CDATA[%s]]></author>}, $_->{author};
			}
			print qq{</authors>};
		}
		my $memos = $self->select_data(qq{select * from books_memo where books_memo.uid=$uid and books_memo.bookid=$_->{bookid} order by books_memo.mtime desc});
		if( $memos->[0]{uid} ){
			print qq{<memo>};
			foreach ( @{$memos} ){
				printf qq{<comment><![CDATA[%s]]></comment>}, $_->{comment};
				printf qq{<netabare>%d</netabare>}, $_->{netabare};
			}
			print qq{</memo>};
		}
		print qq{</book>\n};
	}
	print qq{</bookslist>\n};
	print qq{</dokusho>};

	return;
}
sub download_backup_sqlite{
	my $self = shift;
	my $uid = shift;

	return if ! $uid;
	my $dbname = sprintf qq{user%05d.sqlite}, $uid;
	return if ! -e $self->{root} . $self->{db_path} . $dbname;
	my $fsize = (stat($self->{root} . $self->{db_path} . $dbname))[7];
	my $buf;
	open(IN, $self->{root} . $self->{db_path} . $dbname) || return;
	read(IN, $buf, $fsize);
	close(IN);	

	binmode STDOUT;
	print qq{Content-type: application/octet-stream\n};
	print qq{Content-Disposition: inline; filename=00001.sqlite\n\n};
	print $buf;

	return;
}
1;