#/*
# *  Copyright 2007-2009 hkrn <hikarin@users.sourceforge.jp>
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# */
#
# $Id: Upload.pm 1826 2009-04-08 15:23:25Z hikarin $
#

package Zeromin::Upload;

use strict;
use base qw(Img0ch::Upload);

sub remove {
    my ( $zUpload, $resno ) = @_;
    my $iKernel = $zUpload->{_kernel};

    if ($resno) {
        ( ref $resno || '' ) eq 'ARRAY' or $resno = [$resno];
        my $removed = 0;
        for my $i (@$resno) {
            $i = Img0ch::Kernel::intval($i);
            $zUpload->SUPER::remove( $i, 1 );
            $removed++;
        }
        $zUpload->save();
        return $removed;
    }
    else {
        my $path = $zUpload->path();
        if ( -d $path ) {
            for my $uploaded_file ( @{ $zUpload->traverse() } ) {
                $zUpload->SUPER::remove($uploaded_file);
            }
            $zUpload->save();
            defined $File::Path::VERSION or require File::Path;
            File::Path::rmtree($path)
                or $iKernel->throw_io_exception($path);
            return 1;
        }
        else {
            return 0;
        }
    }

    return 1;
}

sub repair {
    my ( $zUpload, $max ) = @_;
    my $iKernel = $zUpload->{_kernel};

    defined $File::Basename::VERSION or require File::Basename;
    my $path = $zUpload->path();
    -d $path or return 0;

    my $iRepos     = $zUpload->{_rs};
    my $prefix_top = $zUpload->{_prefix};
    my $count      = 0;

    $max ||= 0;
    for ( my $i = 1; $i < $max; $i++ ) {
        my $prefix = join '.', $prefix_top, $i;
        $iRepos->remove("${prefix}.extension");
        $iRepos->remove("${prefix}.width");
        $iRepos->remove("${prefix}.height");
        $iRepos->remove("${prefix}.width.thumbnail");
        $iRepos->remove("${prefix}.height.thumbnail");
        $iRepos->remove("${prefix}.hash");
    }

    foreach my $file ( @{ $zUpload->traverse() } ) {
        next if $file =~ /\.cgi\z/xms or $file =~ /\.s?pch\z/xms;
        if ( $file =~ m{/(\d+)\.(\w+)\z}xms ) {
            my $resno     = $1 || next;
            my $extension = $2 || next;
            my ( $width, $height ) = $zUpload->get_dimensions($file);
            my $hash = $zUpload->get_digest($file);
            my $prefix = join '.', $prefix_top, $resno;
            $iRepos->set( "${prefix}.extension", $extension );
            $iRepos->set( "${prefix}.width",     $width );
            $iRepos->set( "${prefix}.height",    $height );
            $iRepos->set( "${prefix}.hash",      $hash );
            $count++;
        }
        elsif ( $file =~ m{/t(\d+)\.\w+\z}xms ) {
            my $resno = $1 || next;
            my ( $width, $height ) = $zUpload->get_dimensions($file);
            $zUpload->set_thumbnail_size( $resno, $width, $height );
        }
    }

    $zUpload->{_count} = $count;
    $iRepos->set( "${prefix_top}._", $count );
    $iRepos->save();
    return 1;
}

sub get_removed_data {
    my ($zUpload) = @_;
    my $iBBS      = $zUpload->{_i0_bbs};
    my $iKernel   = $iBBS->get_kernel();
    my @repositories;

    defined $File::Basename::VERSION or require File::Basename;
    defined $File::Find::VERSION     or require File::Find;

    File::Find::find(
        {   bydepth  => 1,
            no_chdir => 1,
            untaint  => 1,
            wanted   => sub {
                $File::Find::name =~ m{/upload\.}xms
                    and push @repositories, $File::Find::name;
                }
        },
        File::Basename::dirname( $iBBS->get_repos_path(0) ),
    );

    my $removed_mark = $zUpload->removed_mark();
    my $stack        = [];
    for my $iRepos ( map { $iKernel->get_repos_nocache($_) } @repositories ) {
        $iRepos->iterate(
            sub {
                my ( $key, $value, $stack, $removed_mark, $zUpload ) = @_;
                if (    $$value eq $removed_mark
                    and $key
                    =~ /\AI:U:I\.[^\.]+\.([^\.]+)\.(\d+)\.hash\z/xms )
                {
                    push @$stack, [ $1, $2 ];
                }
            },
            $stack,
            $removed_mark,
            $zUpload,
        );
    }

    return $stack;
}

sub write_removed_data_as_json {
    my ( $zUpload, $path ) = @_;
    my $iBBS    = $zUpload->{_i0_bbs};
    my $iKernel = $iBBS->get_kernel();

    $path ||= $iBBS->path( 'img', 'removed.json' );
    my $fh = $iKernel->get_write_file_handle($path);
    print ${fh} ${ Img0ch::Request::Interface->get_json(
            $zUpload->get_removed_data() ) }
        or $iKernel->throw_io_exception($path);
    close $fh or $iKernel->throw_io_exception($path);

    return 1;
}

sub remove_all_tag_data {
    $_[0]->{_tag}->iterate( sub { return -1 } );
    return 1;
}

sub commit_tag_data {
    $_[0]->{_tag}->save();
    return 1;
}

1;
__END__

=head1 NAME

Zeromin::Upload - アップロードデータの全体的な操作を行うクラス

=head1 SYNOPSYS

  use Zeromin::Upload

  my $zUpload = Zeromin::Upload->new( $iBBS, $key );
  $zUpload->repair();
  $zUpload->remove(19);
  $zUpload->remove([ 2, 5, 11, 17, 31 ]);
  $zUpload->remove_all_tag_data();
  $zUpload->commit_tag_data();
  $zUpload->save();

=head1 DESCRIPTION

L<Img0ch::Upload>を継承してさらに全体的な操作を行うクラスです。

=head2 new

=over 4

=item Arguments

$iBBS (Img0ch::BBS)

=item Return Value

$zUpload (Zeromin::Upload itself)

=back

I<Zeromin::Upload>のオブジェクトを作成します。

=head2 remove

=over 4

=item Arguments

$array_reference_to_reses or $resno

=item Return Value

$removed_file_count or $boolean

=back

引数ありで呼び出した場合は複数のレスに添付されたファイルを削除します。
その場合は削除されたファイル数を返します。

引数なしで呼び出した場合はスレッドにある添付ファイル全てとディレクトリを削除します。
その場合成功した場合は1を、失敗した(ディレクトリが存在しない)場合は0を返します。

=head2 repair

=over 4

=item Arguments

$thread_res_count

=item Return Value

1 or 0

=back

添付ファイルのディレクトリを走査し、添付ファイルデータを修復します。

このメソッドは最低限の作業しか行わないため、直接呼ばないようにしてください。
代わりにL<Zeromin::Thread>のI<repair_upload_file_table>を使用してください。

=head2 remove

=over 4

=item Arguments

$thread_key

=item Return Value

1 or 0

=back

スレッドキーに対応する過去ログを削除します。
レス数が0の過去ログの削除を試みた場合は0を返します。
それ以外にI/Oエラーが発生した場合は例外を送出します。

また、I<zeromin.remove.archive>に対応するプラグインは
このメソッドで呼び出され、実行されます。引数としてスレッドキーが渡されます。

=head2 get_removed_data

=over 4

=item Arguments

none

=item Return Value

$array_reference_to_thread_keys_and_reses

=back

掲示板内で削除された添付ファイルに所属していたスレッドキーとレス番号の
配列のリファレンスを返します。

=head2 write_removed_data_as_json

=over 4

=item Arguments

$path_to_json_file

=item Return Value

1

=back

I<get_removed_data()>が返すデータをI<$path_to_json_file>
またはF<[BBSPath]/[BBSName]/img/removed.json>に
JSONのデータとして書き出します。

=head2 remove_all_tag_data

=over 4

=item Arguments

none

=item Return Value

1

=back

レポジトリに格納されているタグデータをメモリ上から全て削除します。
これを反映させるにはI<commit_tag_data>を使用してください

=head2 commit_tag_data

=over 4

=item Arguments

none

=item Return Value

1

=back

レポジトリのタグデータの内容を保存します。
I<remove_all_tag_data>を使用した後必ずこのメソッドを呼び出してください。

=head1 AUTHOR

hkrn E<lt>hikarin@users.sourceforge.jpE<gt>

=cut
