package Uploader::Images; use strict; use base qw/Uploader::Object/; use Uploader::Image; use Digest::MD5::File qw/file_md5_hex md5_hex/; require Win32 if($^O =~ /MSWin32/); use Storable; use Data::Dumper; $|=1; my @cbk_properties = qw/ add_images_cbk delete_images_cbk create_wx_thumbnail_cbk default_caption_cbk default_caption_pattern_cbk /; my @storable_properties = qw/ ordered_image_ids selection _images current_image selection_tags selection_privacy_level selection_name selection_comment selection_author selection_create_date author type global_rank global_rank_indx /; my @app_properties = qw/ eng_caption_patterns /; my @tmp_properties = qw/ to_be_removed_grank /; __PACKAGE__->mk_accessors(@cbk_properties); __PACKAGE__->mk_accessors(@storable_properties); __PACKAGE__->mk_accessors(@app_properties); __PACKAGE__->mk_accessors(@tmp_properties); sub Init { my ( $self ) = @_; $self->_images({}) if !defined $self->_images; $self->ordered_image_ids([]) if !defined $self->ordered_image_ids; $self->selection([]) if !defined $self->selection; $self->global_rank(-1) if !defined $self->global_rank; $self->global_rank_indx({}) if !defined $self->global_rank_indx; $self->to_be_removed_grank([]) if !defined $self->to_be_removed_grank; } sub add_images { my ( $self, $file_paths ) = @_; my $files = [ map { # to make sure that unicode chars in filenames are supported { ANSIPathName => $^O =~ /MSWin32/ ? Win32::GetANSIPathName($_) : $_, PathName => $_, }, }@$file_paths ]; @$files = sort { $a->{PathName} cmp $b->{PathName} } @$files; foreach my $file ( @{$files} ) { # to append at the end of list $self->add_image_from_file($file); } $self->Store; } sub add_image_from_file { my ( $self, $file ) = @_; # pLoader image id my $image_id = $self->new_image_id( $file->{ANSIPathName} ); my $i = $self->image_count; $self->ordered_image_ids->[$i] = $image_id ; $self->global_rank( 1+$self->global_rank ); $self->set_to_be_removed( $self->global_rank ); $self->global_rank_indx->{ $self->global_rank } = $i; my $image = $self->_images->{ $image_id }; if ( !defined $image ){ $image = Uploader::Image->new( { file => $file->{ANSIPathName}, image_id => $image_id, caption => $self->default_caption_cbk->(), site_author => $self->author, add_rank => $i, site_tags => [], site_high_file => $file->{ANSIPathName}, global_rank => $self->global_rank, } ); $image->site_name( $self->init_caption_from_pattern( $image->file, $image->create_date, $image->add_rank, $self->default_caption_cbk->(), $self->default_caption_pattern_cbk->() ) ); $self->create_wx_thumbnail_cbk->($image) if 'CODE' eq ref $self->create_wx_thumbnail_cbk; $self->_images->{$image_id} = $image ; } $self->add_images_cbk->($image->wx_thumb_file) if 'CODE' eq ref $self->add_images_cbk; $image; } sub new_image_id { my ( $self, $filename ) = @_; my $image_id = file_md5_hex( $filename ); $image_id; } sub Store { my ( $self ) = @_; my $data = $self->get_storable( [ qw/ version storable_file /, @storable_properties ] ); eval { #print Dumper $data; store $data, $self->storable_file if defined $self->storable_file; }; if($@){ print $@, "\n"; } } sub set_current_image { my ( $self, $indx ) = @_; $self->current_image( $indx != -1 ? $self->get_image($indx) : Uploader::Image->new() ); } sub get_image { my ( $self, $indx ) = @_; return unless defined $indx; my $id = $self->ordered_image_ids->[$indx]; $self->_images->{$id}; } sub Image { my ( $self, $id ) = @_; $self->_images->{$id}; } sub is_empty { my ( $self ) = @_; !$self->image_count; } sub image_count { my ( $self ) = @_; scalar @{$self->ordered_image_ids}; } sub selection_count { my ( $self ) = @_; scalar @{$self->selection}; } sub remove_selection { my ( $self ) = @_; return if ( $self->is_empty ); return if (! defined $self->selection ); $self->_remove_selection($self->selection); # clear image selection $self->selection([]); } sub remove_processed { my ( $self ) = @_; return if ( $self->is_empty ); return if (! defined $self->to_be_removed_grank ); my $to_remove_indx = []; # check if the global rank correspond # the image may have changed foreach(@{$self->to_be_removed_grank}) { my $indx = $self->global_rank_indx->{$_}; my $img; if(defined $indx){ $img = $self->get_image($indx); } if(defined $img){ push @$to_remove_indx, $indx if $img->global_rank == $_; } } $self->_remove_selection($to_remove_indx); # clear image selection $self->to_be_removed_grank([]); } sub _remove_selection { my ( $self, $list ) = @_; # the list is sorted, ascendant # we reverse it to have # higher first, and keep same indexes while deleting @$list = reverse @$list; map { $self->delete_image($_); $self->delete_images_cbk->($_); splice @{$self->ordered_image_ids}, $_, 1 ; shift @$list; } @$list; } # postpone actual removal. sub set_to_be_removed { my ( $self, $global_rank ) = @_; push @{$self->to_be_removed_grank}, $global_rank; } sub delete_image { my ( $self, $indx ) = @_; my $id = $self->ordered_image_ids->[$indx]; # have to check if that id is used more than once my $count = grep { $id eq $_} @{$self->ordered_image_ids}; # global rank is unique delete $self->global_rank_indx->{ $self->_images->{$id}->global_rank }; delete $self->_images->{$id} unless $count > 1; } sub multi_selection_mode { my ( $self ) = @_; scalar @{$self->selection} > 1; } sub set_image_selection_tags { my ( $self, $tags ) = @_; $self->selection_tags($tags) if 'ARRAY' eq ref $tags; # append to each image # if multiple selection if($self->multi_selection_mode){ map { # need to dedup my $tags = [ @{$self->get_image($_)->site_tags}, @{$self->selection_tags}, ]; #deduplicate my $uniq = { map { $_ => 1 } @$tags }; @$tags = keys %$uniq; $self->get_image($_)->site_tags( $tags ); }@{$self->selection}; } $self->selection_tags; } sub set_image_selection_privacy_level { my ( $self, $privacy_level ) = @_; # append to each image # if multiple selection if($self->multi_selection_mode){ if(defined $privacy_level){ $self->selection_privacy_level($privacy_level); map { $self->get_image($_)->privacy_level( $privacy_level ) ; }@{$self->selection} }; } $self->selection_privacy_level; } sub set_image_selection_name { my ( $self, $name, $param ) = @_; # works in single and multi selection mode if(defined $name){ map { my $_name; if( 'CODE' eq ref $name ){ $_name = $name->($_, $param); } else{ $_name = $name; $self->get_image($_)->caption( $_name ) ; } $self->selection_name($_name); $self->get_image($_)->site_name( $_name ) ; }@{$self->selection} } } sub set_image_selection_author { my ( $self, $author ) = @_; # append to each image # if multiple selection if($self->multi_selection_mode){ if(defined $author){ $self->selection_author($author); map { $self->get_image($_)->site_author( $author ) ; }@{$self->selection} }; } $self->selection_author; } sub set_image_selection_comment { my ( $self, $comment ) = @_; # append to each image # if multiple selection if($self->multi_selection_mode){ if(defined $comment){ $self->selection_comment($comment); map { $self->get_image($_)->site_comment( $comment ) ; }@{$self->selection} }; } $self->selection_comment; } sub set_image_selection_create_date { my ( $self, $date ) = @_; # append to each image # if multiple selection if($self->multi_selection_mode){ if(defined $date){ $self->selection_create_date($date); map { $self->get_image($_)->create_date( $date ) ; }@{$self->selection} }; } $self->selection_create_date; } sub get_current_image_caption { my ( $self, $index, $pattern ) = @_; $pattern = $self->eng_caption_patterns->{$pattern}; $self->set_current_image($index); my $img = $self->current_image; $self->init_caption_from_pattern( $img->file, $img->create_date, $index, $self->current_image->caption, $pattern ); } sub init_caption_from_pattern { my ( $self, $file, $create_date, $i, $caption, $pattern ) = @_; my ( $yyyy, $mm, $dd, $hh, $mi, $ss ) = split /[:\s]/, $create_date ; my $chrono = join('', $yyyy, $mm, $dd); my $caption_from_pattern; my $ext; my ( $vol, $path, $filename ) = File::Spec->splitpath($file); ( $filename, $ext ) = split /\.\w+$/, $filename; if('Caption' eq $pattern){ $caption_from_pattern = $caption } elsif('File name' eq $pattern){ $caption_from_pattern = $filename } elsif('File path and name' eq $pattern){ $caption_from_pattern = sprintf( "%s", File::Spec->catfile($path, $filename), ) } elsif('Caption + rank number' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", $caption, 1+$i, ) } elsif('Rank number + caption' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", 1+$i, $caption, ) } elsif('Caption + create date chrono' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", $caption, $chrono, ) } elsif('Create date chrono + caption' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", $chrono, $caption, ) } elsif('Create date chrono + rank' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", $chrono, 1+$i, ) } elsif('Rank + create date chrono' eq $pattern){ $caption_from_pattern = sprintf( "%s %s", 1+$i, $chrono, ) } $caption_from_pattern; } # we cannot send objects in other threads # and thread use data as they were when the thread is created sub get_images { my ( $self, $preferences, $destination_category, $all ) = @_; $self->get_images_data( $self->selection_image_ids($all), $preferences, $destination_category ); } sub selection_image_ids { my ( $self, $all ) = @_; $all ? $self->ordered_image_ids : [ map { $self->ordered_image_ids->[$_] }@{$self->selection} ]; } # set piwigo id for already uploaded images. sub set_pwg_id { my ( $self, $pwg_ids ) = @_; map { $self->_images->{$_}->{pwg_image_id} = $pwg_ids->{$_}; } @{$self->ordered_image_ids}; } my $add_rank = 0; # copy data from image objects sub get_images_data { my ( $self, $image_id_list, $preferences, $destination_category ) = @_; return [ map{ $self->Image($_)->get_data($preferences, $destination_category, $add_rank++) }@$image_id_list ] } # when the progress list is cleared sub reset_add_rank { my ( $self ) = @_; $add_rank = 0; } 1;