# +-----------------------------------------------------------------------+ # | pLoader - a Perl photo uploader for Piwigo | # +-----------------------------------------------------------------------+ # | Copyright(C) 2008-2010 Piwigo Team http://piwigo.org | # +-----------------------------------------------------------------------+ # | 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 | # | | # | 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | # | USA. | # +-----------------------------------------------------------------------+ package Uploader::GUI::wxApp; use strict; use base qw/Wx::App Class::Accessor::Fast/; use Wx qw/ wxBITMAP_TYPE_GIF wxBITMAP_TYPE_ICO wxBITMAP_TYPE_BMP wxBITMAP_TYPE_PNG wxBITMAP_TYPE_JPEG wxIMAGE_QUALITY_NORMAL wxIMAGE_QUALITY_HIGH wxSPLASH_CENTRE_ON_SCREEN wxSPLASH_TIMEOUT wxDefaultPosition wxDefaultSize wxSIMPLE_BORDER wxFRAME_TOOL_WINDOW wxFRAME_NO_TASKBAR wxWHITE wxICON_EXCLAMATION wxOK wxLANGUAGE_CHINESE_SIMPLIFIED wxLANGUAGE_CZECH wxLANGUAGE_DANISH wxLANGUAGE_DUTCH wxLANGUAGE_ENGLISH wxLANGUAGE_FRENCH wxLANGUAGE_GERMAN wxLANGUAGE_HUNGARIAN wxLANGUAGE_ITALIAN wxLANGUAGE_JAPANESE wxLANGUAGE_POLISH wxLANGUAGE_PORTUGUESE wxLANGUAGE_PORTUGUESE_BRAZILIAN wxLANGUAGE_RUSSIAN wxLANGUAGE_SLOVAK wxLANGUAGE_SPANISH /; use File::Slurp ; use Data::Dumper; use Storable; use File::HomeDir; use File::Spec; use Uploader::PWG; use Uploader::ImageList; use Uploader::GUI::wxLoginDlg; use Wx::Locale qw/:default/; use utf8; $|=1; __PACKAGE__->mk_accessors( qw/ pwg site_url site_username site_password http_username http_password rejects imagelist storable_file wx_thumb_dir resized_dir userdata_dir resized_dir thumb_dir conf_file layout_file locale current_language languages available_languages version imagelist_version use_offline login_dlg branding frame imageviewerIndex frameLayout chunk_size layout_clean colors eng_colors positions eng_positions caption_patterns eng_caption_patterns upload_hd eng_upload_hd privacy_level user_defined_properties root_dir bin_dir resource_dir locale_dir argv default_openfile_dir / ); sub FilterEvent { my( $self, $event ) = @_; Wx::LogMessage "EventType %s", $event->GetEventType(); return -1; } # not very clean but : # at that moment, object is not yet instanciated # only way : class variable # there is only one instance of wxApp my $root_dir; my $bin_dir; my $resource_dir; my $locale_dir; my $argv; sub new { my ( $self, $params ) = @_; $root_dir = $params->{root_dir}||'.'; $bin_dir = $params->{bin_dir}||'bin'; $locale_dir = $params->{locale_dir}||'locale'; $resource_dir = $params->{resource_dir}||'res'; $argv = $params->{argv}; $self->SUPER::new(); } sub OnInit { my( $self ) = @_; $self->root_dir( $root_dir ); $self->bin_dir( $bin_dir ); $self->locale_dir( $locale_dir ); $self->resource_dir( $resource_dir ); $self->version( '1.5' ); # to check if we can use stored cache $self->imagelist_version( '16' ); $self->argv( $argv ); $self->languages( [ ["中文 (%s)", wxLANGUAGE_CHINESE_SIMPLIFIED, 'Chinese simplified'], ["Česky (%s)", wxLANGUAGE_CZECH, 'Czech'], ["Dansk (%s)", wxLANGUAGE_DANISH, 'Danish'], ["Deutsch (%s)", wxLANGUAGE_GERMAN, 'German'], ["English (%s)", wxLANGUAGE_ENGLISH, 'English'], ["Español (%s)", wxLANGUAGE_SPANISH, 'Spanish'], ["Français (%s)", wxLANGUAGE_FRENCH, 'French'], ["Italiano (%s)", wxLANGUAGE_ITALIAN, 'Italian'], ["日本語 (にほんご) (%s)", wxLANGUAGE_JAPANESE, 'Japanese'], ["Magyar (%s)", wxLANGUAGE_HUNGARIAN, 'Hungarian'], ["Nederlands (%s)", wxLANGUAGE_DUTCH, 'Dutch'], ["Polski (%s)", wxLANGUAGE_POLISH, 'Polish'], ["Português Brasileiro (%s)", wxLANGUAGE_PORTUGUESE_BRAZILIAN, 'Portuguese Brazil'], ["Português Portugal (%s)", wxLANGUAGE_PORTUGUESE, 'Portuguese Portugal'], ["Русский (%s)", wxLANGUAGE_RUSSIAN, 'Russian'], ["Slovenčina (%s)", wxLANGUAGE_SLOVAK, 'Slovak'], ] ); # some languages may be unavailable due to system configuration. $self->_filter_available_languages; Wx::InitAllImageHandlers(); my $applicationName = "pLoader" ; $self->SetAppName( $applicationName ); $self->SetVendorName( "Piwigo Team" ); $self->{IMGTYPE} = { 'jpg' => wxBITMAP_TYPE_JPEG, 'gif' => wxBITMAP_TYPE_GIF, 'png' => wxBITMAP_TYPE_PNG, }; $self->_init_userdir; my $conf = retrieve $self->conf_file if -e $self->conf_file; if(defined $conf ){ $self->SetKeyValues($conf); } else { $self->_readParams( $self->bin_path('pLoader.ini') ) unless defined $conf ; } $self->_read_default_user_properties( $self->bin_path('properties.ini') ) ; $self->site_url( $self->{site_url} ); $self->site_username( $self->{site_username} ); $self->site_password( $self->{site_password} ); $self->http_username( $self->{http_username} ); $self->http_password( $self->{http_password} ); $self->current_language( $self->{current_language}||Wx::Locale::GetSystemLanguage() ); $self->chunk_size( $self->{chunk_size}||500_000 ); $self->init_locale; $self->_init_localized_properties; my $not_exit = $self->Login(); # user pressed OK if($not_exit){ $self->StoreConnectionProperties; if( !$self->use_offline ){ while( $not_exit and !$self->_is_connected ){ $not_exit = $self->Login(); last if $self->use_offline; } } $self->_init_imagelist; $self->_init_thumbimagelist; $self->_set_stored_layout; #$self->_init_frame; } #Wx::LogMessage("bin dir %s", $bin_dir); $not_exit; } # helper method to get the full path for a resource sub resource_path{ my ( $self, $file ) = @_; File::Spec->catfile($self->root_dir, $self->resource_dir, $file); } sub bin_path{ my ( $self, $file ) = @_; File::Spec->catfile($self->root_dir, $self->bin_dir, $file); } sub locale_path{ my ( $self, $file ) = @_; File::Spec->catfile($self->root_dir, $self->locale_dir, $file); } sub locale_catalog_path{ my ( $self, $file ) = @_; File::Spec->catfile($self->root_dir, $self->locale_dir, $self->locale->GetCanonicalName, $file); } sub _set_stored_layout { my ( $self ) = @_; # when language is switched, need a new layout unlink $self->layout_file if $self->layout_clean; $self->layout_clean(0); my $layout = retrieve $self->layout_file if -e $self->layout_file; if(defined $layout ){ $self->SetKeyValues($layout); } } # some properties are displayed in choice list, with translated strings # the translation has to be reverted to store a language independant value sub _init_localized_properties { my ( $self ) = @_; $self->colors( ['Black', 'White'] ); # We need to translate back to english when we store properties $self->eng_colors( { map { gettext($_) => $_ } @{$self->colors} } ); $self->positions( [ 'Top', 'Left', 'Right', 'Bottom', 'Top left', 'Top right', 'Bottom left', 'Bottom right', 'Center', ] ); $self->eng_positions( { map { gettext($_) => $_ } @{$self->positions} } ); $self->upload_hd( [ 'No', 'Yes, use HD resized of the original photo', 'Yes, use a copy of the original photo', ] ); $self->eng_upload_hd( { map { gettext($_) => $_ } @{$self->upload_hd} } ); $self->caption_patterns( [ 'None', 'File name', 'File path and name', 'Caption', 'Caption + rank number', 'Rank number + caption', 'Caption + create date chrono', 'Create date chrono + caption', 'Create date chrono + rank', 'Rank + create date chrono', ] ); $self->eng_caption_patterns( { map { gettext($_) => $_ } @{$self->caption_patterns} } ); # hard coded because the piwigo api to read custom privacy level is not yet available $self->privacy_level( [ 'everybody', 'contacts', 'friends', 'family', 'admins' ] ); } # display privacy level list in a pyramid way : # ['everybody', 'contacts', friends, family, admins] -> [everybody, 'contacts, friends, family, admins', 'friends, family, admins', 'family, admins', 'admins only' ] sub privacy_level_choices{ my ( $self ) = @_; my $pl = $self->privacy_level; my $n = scalar @$pl - 1; my $list = [ gettext($pl->[0]) ]; my $i=0; while(++$i<$n){ push @$list, join( ', ', map{ gettext($_) } @$pl[$i..$n] ); } push @$list, gettext($pl->[$n]); $list; } sub _filter_available_languages { my ( $self ) = @_; # check if the locale can be set and the translation catalog available $self->available_languages( [ grep {$_} map{ # a locale may be unavailable due to system limitations ( ex: chinese, japanese when language pack are not installed ) if(Wx::Locale::IsAvailable($_->[1])){ my $locale = Wx::Locale->new($_->[1]); $locale->AddCatalogLookupPathPrefix( File::Spec->catfile($self->root_dir, $self->locale_dir) ); $_ if $locale->AddCatalog('pLoader'); } } @{$self->languages} ] ); } sub _is_connected { my ( $self ) = @_; my $is_connected; if($self->pwg->login_result->{stat} eq 'ok'){ $is_connected = 1; } else{ Wx::MessageBox( sprintf( "%s\n\n%s %s %s", $self->pwg->login_result->{message}, gettext("Connection to"), $self->site_url, gettext("failed"), ), gettext("Piwigo login error"), wxOK | wxICON_EXCLAMATION, ); } $is_connected; } my $locale; sub init_locale { my ( $self, $language ) = @_; $self->current_language( $language ) if defined $language; undef $locale; $locale = Wx::Locale->new( $self->current_language ); $locale->AddCatalogLookupPathPrefix( File::Spec->catfile($self->root_dir, $self->locale_dir) ); if(!$locale->AddCatalog( 'pLoader.mo' )){ Wx::LogMessage gettext("Cannot find translation catalog files for %s. Use default language"), $locale->GetCanonicalName(); } $self->locale($locale); } sub StoreConnectionProperties { my ( $self ) = @_; eval { store( { map{ $_ => $self->{$_}, } qw/ site_url site_username site_password http_username http_password current_language chunk_size layout_clean default_openfile_dir / }, $self->conf_file ); }; } sub StoreLayoutProperties { my ( $self ) = @_; eval { store( { map{ $_ => $self->{$_}, } qw/ imageviewerIndex frameLayout / }, $self->layout_file ); }; } sub _init_imagelist { my ( $self ) = @_; my $stored_imagelist; my $use_new_imagelist; if( -e $self->storable_file ){ eval { $stored_imagelist = retrieve $self->storable_file; }; if($@){ Wx::LogMessage( gettext("An error has occured. Can not read %s\n%s"), $self->storable_file, $@ ); $use_new_imagelist = 1 ; } # should have a valid imagelist else{ $use_new_imagelist = 1 unless $self->imagelist_version eq $stored_imagelist->{imagelist_version}; } $use_new_imagelist = 1 unless defined $stored_imagelist; if($use_new_imagelist){ Wx::LogMessage(gettext("pLoader has to reset image cache.")); # and silently clean layout $self->layout_clean(1); } } else{ $use_new_imagelist = 1 ; } if($use_new_imagelist){ $stored_imagelist = $self->_default_imagelist_params ; } $self->imagelist( Uploader::ImageList->new( $stored_imagelist ) ); $self->imagelist->SetCurrentImage(-1); $self->imagelist->ResizeCallback( sub { $self->ResizeImage(@_) } ); $self->imagelist->YieldCallback( sub { Wx::Yield } ); } sub _default_imagelist_params { my ( $self ) = @_ ; my $params = { new_files => [], hd_filter => $self->user_defined_properties->{hd_filter}||'Lanczos', hd_blur => $self->user_defined_properties->{hd_blur}||0.9, hd_quality => $self->user_defined_properties->{hd_quality}||95, hd_w => $self->user_defined_properties->{hd_w}||1600, hd_h => $self->user_defined_properties->{hd_h}||1200, hd_interlace => $self->user_defined_properties->{hd_interlace}||'Line', thumb_size => $self->user_defined_properties->{thumbnail_size}||120, site_thumb_dir => $self->thumb_dir, wx_thumb_size => $self->user_defined_properties->{wx_thumbnail_size}||100, wx_thumb_dir => $self->wx_thumb_dir, resize_w => $self->user_defined_properties->{resize_w}||800, resize_h => $self->user_defined_properties->{resize_h}||600, site_resized_dir => $self->resized_dir, type => 'jpg', filter => $self->user_defined_properties->{resize_filter}||'Lanczos', blur => $self->user_defined_properties->{resize_blur}||0.9, quality => $self->user_defined_properties->{resize_quality}||95, wx_quality => $self->user_defined_properties->{wx_thumbnail_quality}||90, th_quality => $self->user_defined_properties->{thumbnail_quality}||90, auto_rotate => $self->user_defined_properties->{auto_rotate}||1, upload_hd => $self->user_defined_properties->{upload_hd}||'No', remove_uploaded_from_selection => $self->user_defined_properties->{remove_uploaded_from_selection}||1, interlace => $self->user_defined_properties->{resize_interlace}||'Line', create_resized => $self->user_defined_properties->{create_resized}||1, prefix => 'TN', count => 0, storable_file => $self->storable_file, userdata_dir => $self->userdata_dir, default_caption_pattern => $self->user_defined_properties->{default_caption_pattern}||'File name', default_caption => $self->user_defined_properties->{default_caption}||gettext('Photo '), upload_rejects => [], image_sums => {}, sums => [], version => $self->version, imagelist_version => $self->imagelist_version, ResizeCallback => sub { $self->ResizeImage(@_) }, watermark_text => $self->user_defined_properties->{watermark_text}||gettext("my watermark"), watermark_text_size => $self->user_defined_properties->{watermark_text_size}||12, watermark_position => $self->user_defined_properties->{watermark_position}||'Center', watermark_y => $self->user_defined_properties->{watermark_y}||10, watermark_x => $self->user_defined_properties->{watermark_x}||10, watermark_color => $self->user_defined_properties->{watermark_color}||'White', reupload_action_files => 1, reupload_action_properties => 2, reupload_action_properties_m => 1, display_mode => $self->user_defined_properties->{display_mode}||'Thumbnail and caption', }; return $params; } sub Login { my ( $self ) = @_; $self->login_dlg( Uploader::GUI::wxLoginDlg->new( { title => gettext("Piwigo login"), site_url => sub { $self->site_url(@_) }, site_username => sub { $self->site_username(@_) }, site_password => sub { $self->site_password(@_) }, use_offline => sub { $self->use_offline(@_) }, } ) ) unless $self->login_dlg; my $icon = Wx::Icon->new(); $icon->LoadFile( $self->resource_path('favicon.ico'), wxBITMAP_TYPE_ICO ); $self->login_dlg->SetIcon($icon); my $rval = $self->login_dlg->ShowModal(); $self->login_dlg->Show(0); $self->_init_branding; if ($self->site_url !~ /^http:/){ $self->site_url( sprintf( "http://%s", $self->site_url ) ); } $self->pwg( # get these parameters from dialog or from file Uploader::PWG->new( { site_url => $self->site_url, site_username => $self->site_username, site_password => $self->site_password, http_username => $self->http_username, http_password => $self->http_password, branding => $self->branding, chunk_size => sub { $self->chunk_size(@_) }, use_offline => $self->use_offline, version => $self->version, } ) ); $rval; } sub _init_userdir { my ( $self ) = @_; my $applicationName = $self->GetAppName ; my $userdatadir = File::Spec->canonpath( File::Spec->catfile( File::HomeDir->my_data(), "\.$applicationName" ) ); if(! -d $userdatadir){ if(! mkdir $userdatadir){ Wx::MessageBox( sprintf( "%s directory creation failed", $userdatadir, ), "pLoader working directory creation error", wxOK | wxICON_EXCLAMATION, ); $userdatadir = File::Spec->canonpath( File::Spec->catfile( File::Spec->tmpdir(), "\.$applicationName" ) ); mkdir $userdatadir; } } $self->userdata_dir($userdatadir); $self->conf_file( File::Spec->catfile( $self->userdata_dir, ".$applicationName.conf" ) ); $self->layout_file( File::Spec->catfile( $self->userdata_dir, ".$applicationName.layout" ) ); $self->storable_file( File::Spec->catfile($self->userdata_dir, 'pLoader.dat') ); my $thumbdir = File::Spec->catfile($self->userdata_dir, 'thumbnails'); mkdir $thumbdir unless -d $thumbdir ; $self->thumb_dir($thumbdir); my $wxthumbdir = File::Spec->catfile($self->userdata_dir, 'wxthumbnails'); mkdir $wxthumbdir unless -d $wxthumbdir ; $self->wx_thumb_dir($wxthumbdir); my $resizedir = File::Spec->catfile($self->userdata_dir, 'resize'); mkdir $resizedir unless -d $resizedir ; $self->resized_dir($resizedir); } sub _init_thumbimagelist { my ( $self ) = @_; $self->imagelist->wx_thumb_imglist( Wx::ImageList->new( $self->imagelist->wx_thumb_size, $self->imagelist->wx_thumb_size, 1, 0 ) ); # reload images $self->_reload_thumb_images; } sub _reload_thumb_images { my ( $self ) = @_; my $wximagelist = $self->imagelist->wx_thumb_imglist; my $sums = $self->imagelist->sums; map { my $image = $self->imagelist->image_sums->{$_}; $wximagelist->Add( Wx::Bitmap->new( $image->wx_thumb_file, $self->GetWxBitmapType($self->imagelist->type), ) ); } @$sums ; } sub GetWxBitmapType { my ( $self, $type ) = @_; $self->{IMGTYPE}->{$type}; } sub ResizeImage { my ( $self, $image_file, $image_file_out, $type, $width, $height, $quality ) = @_; my $image = Wx::Image->new( $image_file, $self->GetWxBitmapType($type), 0 ); my $w; my $h; my $img_w = $image->GetWidth; my $img_h = $image->GetHeight; # portrait if( $img_w < $img_h ){ $w = $height; } else{ $w = $width; } # to respect aspect ratio $h = sprintf( "%.0f", ($w*$img_h)/$img_w ); $image->Rescale( $w, $h, wxIMAGE_QUALITY_HIGH ); #$image->Resize( # [ $width, $height ], [ 0, 20], #); $quality ||= 90; $image->SetOption( "quality", $quality ); if(!$image->SaveFile( $image_file_out, $self->GetWxBitmapType($type), )){ Wx::LogMessage( gettext("An error has occured. Can not save file %s"), $image_file_out, ) }; } # some labels differ with branding ( piwigo.com or piwigo.org ) sub _init_branding { my ( $self ) =@_; if( $self->site_url =~ /\.piwigo\.com/ ){ $self->branding( { category => gettext("album"), Category => gettext("Album"), categories => gettext("albums"), Categories => gettext("Albums"), 'Add new category' => gettext("Add new album"), 'Category name' => gettext("Album name :"), 'New category' => gettext("New album"), 'What is the destination category?' => gettext("What is the destination album?") } ); } else{ $self->branding( { category => gettext("categorie"), Category => gettext("Categorie"), categories => gettext("categories"), Categories => gettext("Categories"), 'Add new category' => gettext("Add new category"), 'Category name' => gettext("Category name :"), 'New category' => gettext("New category"), 'What is the destination category?' => gettext("What is the destination category?") } ); } } sub SetFrame { my ( $self, $frame ) = @_; my $url = $self->site_url; if($self->use_offline){ $url = gettext("Work Offline"); } $self->frame($frame); my $icon = Wx::Icon->new(); $icon->LoadFile( File::Spec->catfile( $self->root_dir, $self->resource_dir, 'favicon.ico' ), wxBITMAP_TYPE_ICO ); $self->frame->SetIcon($icon); } sub _readParams { my( $self, $file ) = @_ ; my $expr_params ; eval { $expr_params = read_file( $file ); } ; my $paramValues = [] ; if($expr_params){ my $expr = '$paramValues = ' ; $expr .= "$expr_params ; " ; eval $expr ; } return unless 'ARRAY' eq ref $paramValues ; if(scalar(@$paramValues )){ my $params = $paramValues->[0] ; $self->SetKeyValues($params); } } sub _read_default_user_properties { my( $self, $file ) = @_ ; my $expr_params ; eval { $expr_params = read_file( $file ); } ; my $properties; if($expr_params){ my $expr = '$properties = ' ; $expr .= " { $expr_params }; " ; eval $expr ; } $properties = {} unless 'HASH' eq ref $properties; $self->user_defined_properties($properties); } sub SetKeyValues { my ( $self, $params )= @_; foreach( keys %$params ) { $self->{$_} = $params->{$_} ; } } 1;