source: extensions/pLoader/trunk/src/Uploader/GUI/App.pm @ 6426

Last change on this file since 6426 was 6426, checked in by ronosman, 14 years ago

Bug 1710 fixed : pLoader lack of concurrency support causes data inconsistency.

File size: 35.3 KB
Line 
1package Uploader::GUI::App;
2use threads;
3use threads::shared;
4use Thread::Queue;
5use strict;
6use base qw/Wx::App Class::Accessor::Fast/;
7use Wx qw/
8    wxBITMAP_TYPE_GIF
9    wxBITMAP_TYPE_ICO
10    wxBITMAP_TYPE_BMP
11    wxBITMAP_TYPE_PNG
12    wxBITMAP_TYPE_JPEG
13    wxIMAGE_QUALITY_HIGH
14    wxSPLASH_CENTRE_ON_SCREEN
15    wxSPLASH_TIMEOUT
16    wxDefaultPosition
17    wxDefaultSize
18    wxSIMPLE_BORDER
19    wxFRAME_TOOL_WINDOW
20    wxFRAME_NO_TASKBAR wxSTAY_ON_TOP
21    wxWHITE
22    wxICON_EXCLAMATION
23    wxOK
24    wxICON_ERROR
25    wxLANGUAGE_CHINESE_SIMPLIFIED
26    wxLANGUAGE_CZECH
27    wxLANGUAGE_DANISH
28    wxLANGUAGE_DUTCH
29    wxLANGUAGE_ENGLISH
30    wxLANGUAGE_FRENCH
31    wxLANGUAGE_GERMAN
32    wxLANGUAGE_HUNGARIAN
33    wxLANGUAGE_ITALIAN
34    wxLANGUAGE_JAPANESE
35    wxLANGUAGE_POLISH
36    wxLANGUAGE_PORTUGUESE
37    wxLANGUAGE_PORTUGUESE_BRAZILIAN
38    wxLANGUAGE_RUSSIAN
39    wxLANGUAGE_SLOVAK
40    wxLANGUAGE_SPANISH
41/;
42use Wx::Locale qw/:default/;
43use File::HomeDir;
44use Uploader::PWG;
45use Uploader::Images;
46use Uploader::Connection;
47use Uploader::Preferences;
48use Uploader::ResizeManager;
49use Uploader::TransferManager;
50use Uploader::GUI::LoginDlg;
51use Storable;
52use utf8;
53use Data::Dumper;
54use IO::Socket;
55use Wx::Socket;
56
57my $received_filename_event : shared = Wx::NewEventType;
58my $transfer_progress_event : shared = Wx::NewEventType;
59my $transfer_done_event : shared = Wx::NewEventType;
60my $resize_done_event : shared = Wx::NewEventType;
61my $resize_progress_event : shared = Wx::NewEventType;
62
63my $image_progress_event = Wx::NewEventType;
64
65$|=1;
66
67
68my @app_properties = qw/
69    version
70    frame
71    argv
72    images
73    images_version
74    preferences
75    preferences_version
76    user_def_preferences
77    privacy_level
78    upload_hd
79    resize_manager
80    transfer_manager
81/;
82
83
84my @connection_properties = qw/
85    connection_version
86    connection
87    login_dlg
88    branding
89    use_offline
90    use_connected
91    pwg
92/;
93
94
95my @file_properties = qw/
96    bin_dir
97    resource_dir
98    thumb_dir
99    resized_dir
100    wx_thumb_dir
101    userdata_dir
102    root_dir
103    locale_dir
104    preferences_file
105    connection_file
106    images_file
107    app_file
108/;
109
110
111my @localized_properties = qw/
112    colors
113    positions
114    upload_hd
115    caption_patterns
116/;
117
118
119my @language_properties = qw/
120    current_language
121    locale
122    languages
123    available_languages
124    eng_colors
125    eng_positions
126    eng_upload_hd
127    eng_caption_patterns
128/;
129
130
131my @transfer_properties = qw/
132    transfer_thread
133    transfer_thread_queue
134    resize_thread
135    resize_thread_queue
136/;
137
138my @custom_events = qw/
139    resize_start_event
140    image_progress_event
141/;
142
143__PACKAGE__->mk_accessors(@app_properties);
144__PACKAGE__->mk_accessors(@file_properties);
145__PACKAGE__->mk_accessors(@connection_properties);
146__PACKAGE__->mk_accessors(@localized_properties);
147__PACKAGE__->mk_accessors(@language_properties);
148__PACKAGE__->mk_accessors(@transfer_properties);
149__PACKAGE__->mk_accessors(@custom_events);
150
151my $_params;
152
153sub new {
154    my ( $self, $params ) = @_;
155
156    $_params = $params;
157
158    $self->SUPER::new();
159
160}
161
162
163sub OnInit {
164    my( $self ) = @_;
165
166    $self->init_properties_from_params;
167    $self->init_application_properties;
168    $self->init_single_instance_checker;
169    $self->init_file_properties;
170    $self->init_locale;
171    $self->init_localized_properties;
172    $self->init_preferences;
173    $self->init_resize_manager;
174    $self->init_images;
175
176    return 0 unless $self->is_single_instance_server;
177
178    $self->connect_or_exit;
179
180}
181
182
183sub OnExit {
184    my( $self ) = @_;
185
186    $self->store_all;
187
188}
189
190
191sub store_all {
192    my( $self ) = @_;
193
194    $self->connection->Store;;
195    $self->images->Store;
196    $self->preferences->Store;
197
198}
199
200
201sub cancel_queues {
202    my ( $self ) = @_;
203
204    $self->cancel_resize;
205    $self->cancel_transfer;
206}
207
208
209sub stop_all {
210    my( $self ) = @_;
211
212    $self->stop_resize_manager;
213    $self->stop_transfer_manager;
214    $self->stop_single_instance_server;
215    $self->login_dlg->Destroy;   
216
217
218
219}
220
221
222sub stop_queues {
223    my ( $self ) = @_;
224
225    $self->stop_resize;
226    $self->stop_transfer;
227}
228
229
230sub stop_resize_manager {
231    my( $self ) = @_;
232
233    $self->stop_resize;
234    $self->resize_thread->detach;
235}
236
237
238sub stop_transfer_manager {
239    my( $self ) = @_;
240
241    $self->stop_transfer;
242    $self->transfer_thread->detach;
243}
244
245sub cancel_transfer {
246    my( $self ) = @_;
247   
248    my $data : shared = "CANCEL";
249
250    $self->transfer_thread_queue->insert(0, $data);
251}
252
253
254sub stop_transfer {
255    my( $self ) = @_;
256   
257    my $data : shared = "STOP";
258
259    $self->transfer_thread_queue->insert(0, $data);
260}
261
262
263sub init_properties_from_params {
264    my ( $self ) = @_;
265
266    map {
267        $self->$_(
268            $_params->{$_}
269        )
270    } keys %$_params;
271
272}
273
274
275sub init_application_properties {
276    my ( $self ) = @_;
277
278    $self->version(
279        '1.7'
280    );
281   
282
283    Wx::InitAllImageHandlers();
284
285    $self->{IMGTYPE} = {
286        'jpg' => wxBITMAP_TYPE_JPEG,
287        'gif' => wxBITMAP_TYPE_GIF,
288        'png' => wxBITMAP_TYPE_PNG,
289    };
290
291    my $applicationName = "pLoader" ;
292    $self->SetAppName( $applicationName );
293    $self->SetVendorName( "Piwigo Team" );
294
295
296}
297
298
299sub init_single_instance_checker {
300    my ( $self ) = @_;
301
302
303    my $id_user=$ENV{USERNAME} || $ENV{USER} || $ENV{LOGNAME};
304    # must save the object
305    $self->{_instance_checker} = Wx::SingleInstanceChecker->new;
306    my $pid = sprintf(
307        "%s-%s",
308        $self->GetAppName,
309        $id_user,
310    );
311    $self->{_instance_checker}->Create(
312        $pid
313    );
314}
315
316
317sub init_file_properties {
318    my ( $self ) = @_;
319   
320    my $applicationName = $self->GetAppName ;
321    my $userdatadir = File::Spec->canonpath(
322        File::Spec->catfile(
323            File::HomeDir->my_data(), 
324            $applicationName
325        )
326    );
327
328    if(! -d $userdatadir){
329        if(! mkdir $userdatadir){
330            Wx::MessageBox( 
331                sprintf(
332                    "%s directory creation failed",
333                    $userdatadir,
334                ),
335                "pLoader working directory creation error",
336                wxOK | wxICON_EXCLAMATION, 
337            );
338
339            $userdatadir = File::Spec->canonpath(
340                File::Spec->catfile(
341                    File::Spec->tmpdir(), 
342                    $applicationName
343                )
344            );
345            mkdir $userdatadir;
346        }   
347    }
348
349    $self->userdata_dir($userdatadir);
350
351    $self->app_file(
352        $self->userdata_filepath("$applicationName.dat")
353    );
354
355    $self->preferences_file(
356        $self->userdata_filepath("preferences.dat")
357    );
358
359    $self->connection_file(
360        $self->userdata_filepath("connection.dat")
361    );
362
363    $self->images_file(
364        $self->userdata_filepath("images.dat")
365    );
366   
367    my $thumbdir = $self->userdata_filepath('thumbnails');
368    mkdir $thumbdir unless -d $thumbdir ;
369    $self->thumb_dir($thumbdir);
370
371    my $wxthumbdir = $self->userdata_filepath('wxthumbnails');
372    mkdir $wxthumbdir unless -d $wxthumbdir ;
373    $self->wx_thumb_dir($wxthumbdir);
374
375
376    my $resizedir = $self->userdata_filepath('resize');
377    mkdir $resizedir unless -d $resizedir ;
378    $self->resized_dir($resizedir);
379}
380
381
382sub userdata_filepath {
383    my ( $self, $filename ) = @_;
384
385    File::Spec->catfile(
386        $self->userdata_dir,
387        $filename
388    );
389}
390
391
392sub read_params {
393    my( $self, $file ) = @_ ;
394
395 
396    my $expr_params ;
397    eval { $expr_params = read_file( $file ); } ;
398   
399    my $paramValues = [] ;
400    if($expr_params){
401        my $expr = '$paramValues = ' ;
402        $expr .=  "$expr_params ; " ;
403        eval $expr ;
404    }
405   
406    return unless 'ARRAY' eq ref $paramValues ;
407   
408    if(scalar(@$paramValues )){
409        my $params = $paramValues->[0] ;
410        $self->set_key_values($params);
411    }
412
413}
414
415
416sub set_key_values {
417    my ( $self, $params )= @_;   
418
419    foreach( keys %$params ) {
420        $self->{$_} = $params->{$_} ;
421    }
422}
423
424
425sub init_connection {
426    my ( $self ) = @_;
427
428    $self->connection_version('0.1');
429
430    my $stored = $self->retrieve_from_file(
431        $self->connection_file,
432        $self->connection_version
433    );
434
435    $self->connection(
436        $stored ?
437        Uploader::Connection->new(
438            $stored
439        ):
440        Uploader::Connection->new(
441            $self->default_connection
442        )
443    );
444
445}
446
447
448sub default_connection {
449    my ( $self ) = @_;
450
451    my $connection = {
452        version          => $self->connection_version,
453        storable_file    => $self->connection_file,
454    };
455
456    return $connection;
457}
458
459my $locale;
460sub init_locale {
461    my ( $self, $language ) = @_;
462
463    $self->languages(
464      [
465             ["中文 (%s)", wxLANGUAGE_CHINESE_SIMPLIFIED, 'Chinese simplified'],   
466             ["Česky (%s)", wxLANGUAGE_CZECH, 'Czech'],   
467             ["Dansk (%s)", wxLANGUAGE_DANISH, 'Danish'],   
468             ["Deutsch (%s)", wxLANGUAGE_GERMAN, 'German'],   
469             ["English (%s)", wxLANGUAGE_ENGLISH, 'English'],   
470             ["Español (%s)", wxLANGUAGE_SPANISH, 'Spanish'],   
471             ["Français (%s)", wxLANGUAGE_FRENCH, 'French'],   
472             ["Italiano (%s)", wxLANGUAGE_ITALIAN, 'Italian'],   
473             ["日本語 (にほんご) (%s)", wxLANGUAGE_JAPANESE, 'Japanese'],   
474             ["Magyar (%s)", wxLANGUAGE_HUNGARIAN, 'Hungarian'],
475             ["Nederlands (%s)", wxLANGUAGE_DUTCH, 'Dutch'],   
476             ["Polski (%s)", wxLANGUAGE_POLISH, 'Polish'],   
477             ["Português Brasileiro (%s)", wxLANGUAGE_PORTUGUESE_BRAZILIAN, 'Portuguese Brazil'],   
478             ["Português Portugal (%s)", wxLANGUAGE_PORTUGUESE, 'Portuguese Portugal'],   
479             ["Русский (%s)", wxLANGUAGE_RUSSIAN, 'Russian'],
480             ["Slovenčina (%s)", wxLANGUAGE_SLOVAK, 'Slovak'],
481      ]
482    );
483    # some languages may be unavailable due to system configuration.
484    $self->filter_available_languages;
485
486    $self->current_language(
487        $language||$self->{current_language}||Wx::Locale::GetSystemLanguage()
488    );
489
490    undef $locale;
491    $locale = Wx::Locale->new(
492        $self->current_language
493    );
494    $locale->AddCatalogLookupPathPrefix(
495        File::Spec->catfile($self->root_dir, $self->locale_dir)
496    );
497    if(!$locale->AddCatalog( 'pLoader.mo' )){
498        Wx::LogMessage gettext("Cannot find translation catalog files for %s. Use default language"), $locale->GetCanonicalName();
499    }
500    $self->locale($locale);   
501}
502
503
504sub filter_available_languages {
505    my ( $self ) = @_;
506
507    # check if the locale can be set and the translation catalog available
508    $self->available_languages(
509        [
510            grep {$_} 
511            map{
512                #  a locale may be unavailable due to system limitations ( ex: chinese, japanese when language pack are not installed )
513                if(Wx::Locale::IsAvailable($_->[1])){
514                    my $locale = Wx::Locale->new($_->[1]);
515                    $locale->AddCatalogLookupPathPrefix(
516                        File::Spec->catfile($self->root_dir, $self->locale_dir)
517                    );
518                    $_ if $locale->AddCatalog('pLoader');
519                }
520            }
521            @{$self->languages}
522        ]
523    );
524}
525
526
527sub init_localized_properties {
528    my ( $self ) = @_;
529
530    $self->colors(
531        ['Black', 'White']
532    );
533
534    # We need to translate back to english when we store properties
535    $self->eng_colors(
536        {
537            map { gettext($_) => $_ } @{$self->colors} 
538        }
539    );
540
541    $self->positions(
542        [
543            'Top',
544            'Left',
545            'Right',
546            'Bottom',
547            'Top left',
548            'Top right',
549            'Bottom left',
550            'Bottom right',
551            'Center',
552        ]
553    );
554
555    $self->eng_positions(
556        { 
557             map { gettext($_) => $_ } @{$self->positions} 
558        }
559    );
560
561    $self->upload_hd(
562        [
563            'No',
564            'Yes, use HD resized of the original photo',
565            'Yes, use a copy of the original photo',
566        ]
567    );
568
569    $self->eng_upload_hd(
570        {
571             map { gettext($_) => $_ } @{$self->upload_hd} 
572        }
573    );
574   
575
576    $self->caption_patterns(
577        [
578             'None',
579             'File name',
580             'File path and name',
581             'Caption',
582             'Caption + rank number',
583             'Rank number + caption',
584             'Caption + create date chrono',
585             'Create date chrono + caption',
586             'Create date chrono + rank',
587             'Rank + create date chrono',
588        ]
589    );
590
591    $self->eng_caption_patterns(
592        {
593             map { gettext($_) => $_ } @{$self->caption_patterns} 
594        }
595    );
596   
597    # hard coded because the piwigo api to read custom privacy level is not yet available
598    $self->privacy_level(
599        [
600            'everybody',
601            'contacts',
602            'friends',
603            'family',
604            'admins'
605        ]
606    );
607
608}
609
610
611# display privacy level list in a pyramid way :
612# ['everybody', 'contacts', friends, family, admins] -> [everybody, 'contacts, friends, family, admins', 'friends, family, admins', 'family, admins', 'admins only' ]
613sub privacy_level_choices{
614    my ( $self ) = @_;
615
616    my $pl = $self->privacy_level;   
617    my $n = scalar @$pl - 1;
618    my $list = [ gettext($pl->[0]) ];
619    my $i=0;
620    while(++$i<$n){   
621        push @$list, join(
622            ', ', 
623            map{ gettext($_) }
624            @$pl[$i..$n] 
625        );
626    }
627    push @$list, gettext($pl->[$n]);
628   
629    $list;
630}
631
632
633sub init_preferences {
634    my ( $self ) = @_;   
635
636    $self->preferences_version('0.1');
637
638    my $stored = $self->retrieve_from_file(
639        $self->preferences_file,
640        $self->preferences_version
641    );
642
643    $self->preferences(
644        $stored ?
645        Uploader::Preferences->new(
646            $stored
647        ):
648        Uploader::Preferences->new(
649            $self->default_preferences
650        )
651    );
652}
653
654
655sub retrieve_from_file {
656    my ( $self, $file, $version ) = @_;
657
658
659    my $stored;
660   
661    if( -e $file ){
662        eval {
663            $stored = retrieve $file;
664        };
665        if($@){
666            warn(
667                "An error has occured. Can not read %s\n%s",
668                $file, 
669                $@
670            );
671            undef $stored;
672        }
673        # should have a valid images
674        else{
675            undef $stored unless $version eq $stored->{version};
676        }
677    }
678
679    $stored;
680}
681
682
683sub default_preferences {
684    my ( $self ) = @_ ;
685
686    # must be read in a ini file
687    $self->user_def_preferences(
688        {}
689    );
690
691    my $preferences = {
692        version => $self->preferences_version,
693        storable_file    => $self->preferences_file,
694        hd_filter        => $self->user_def_preferences->{hd_filter}||'Lanczos',
695        hd_blur          => $self->user_def_preferences->{hd_blur}||0.9,
696        hd_quality       => $self->user_def_preferences->{hd_quality}||95,
697        hd_w             => $self->user_def_preferences->{hd_w}||1600,
698        hd_h             => $self->user_def_preferences->{hd_h}||1200,
699        hd_interlace     => $self->user_def_preferences->{hd_interlace}||'Line',
700        thumb_size       => $self->user_def_preferences->{thumbnail_size}||120,
701        wx_thumb_size    => $self->user_def_preferences->{wx_thumbnail_size}||100,
702        resize_w         => $self->user_def_preferences->{resize_w}||800,
703        resize_h         => $self->user_def_preferences->{resize_h}||600,
704        type             => 'jpg',
705        filter           => $self->user_def_preferences->{resize_filter}||'Lanczos',
706        blur             => $self->user_def_preferences->{resize_blur}||0.9,
707        quality          => $self->user_def_preferences->{resize_quality}||95,
708        wx_quality       => $self->user_def_preferences->{wx_thumbnail_quality}||90,
709        th_quality       => $self->user_def_preferences->{thumbnail_quality}||90,
710        auto_rotate      => $self->user_def_preferences->{auto_rotate}||1,
711        upload_hd        => $self->user_def_preferences->{upload_hd}||'No',
712        remove_uploaded_from_selection => $self->user_def_preferences->{remove_uploaded_from_selection}||1,
713        interlace        => $self->user_def_preferences->{resize_interlace}||'Line',
714        create_resized   => $self->user_def_preferences->{create_resized}||1,
715        prefix           => 'TN',
716        default_caption_pattern => $self->user_def_preferences->{default_caption_pattern}||'File name',
717        default_caption  => $self->user_def_preferences->{default_caption}||gettext('Photo '),
718        watermark_text => $self->user_def_preferences->{watermark_text}||gettext("my watermark"),
719        watermark_text_size => $self->user_def_preferences->{watermark_text_size}||12,
720        watermark_position => $self->user_def_preferences->{watermark_position}||'Center',
721        watermark_y => $self->user_def_preferences->{watermark_y}||10,
722        watermark_x => $self->user_def_preferences->{watermark_x}||10,
723        watermark_color => $self->user_def_preferences->{watermark_color}||'White',
724        reupload_action_files => 1,
725        reupload_action_properties => 2,
726        reupload_action_properties_m => 1,
727        display_mode => $self->user_def_preferences->{display_mode}||'Thumbnail and caption',
728        chunk_size   => $self->user_def_preferences->{chunk_size}||500000,
729    };
730
731    return $preferences;
732}
733
734
735sub init_resize_manager {
736    my ( $self ) = @_;
737
738    $self->resize_start_event(
739        Wx::NewEventType
740    );
741
742    $self->resize_manager(
743        Uploader::ResizeManager->new({
744            site_thumb_dir   => $self->thumb_dir,
745            wx_thumb_dir     => $self->wx_thumb_dir,
746            site_resized_dir => $self->resized_dir,
747            preferences      => sub { $self->preferences(@_) },
748            write_type       => 'jpg',
749        })
750    );
751
752    $self->init_resize_thread_queue;
753    $self->init_resize_done_event_handler;
754    $self->init_resize_progress_event_handler;
755}
756
757
758sub init_resize_thread_queue {
759    my ( $self ) = @_;
760
761    $self->resize_thread_queue(
762        Thread::Queue->new()
763    );
764
765    # resize worker starts when receiving data from the resize queue
766    # resize data must be sent to the calling $handler because $handler data
767    # cannot be updated inside the thread
768    $self->resize_thread(
769        $self->new_resize_thread
770    );
771
772}
773
774
775sub new_resize_thread {
776    my ( $self ) = @_;
777
778    threads->create( 
779        sub { 
780            my ( $handler, $resize_manager, $queue ) = @_;
781            while (my $data = $queue->dequeue ) {
782               
783                return 1 if 'STOP' eq $data;
784                if('CANCEL' eq $data){
785                    # remove pending
786                    while($queue->pending){
787                        # must check if valid $image hash
788                        $resize_manager->cancel_image(
789                            $queue->dequeue
790                        );
791                    }
792                }
793                else{
794                    $resize_manager->process_image(
795                        $handler,
796                        $resize_progress_event,
797                        $resize_done_event,
798                        $data
799                    );
800                }
801            }
802            printf("resize queue %s\n", $queue->pending);
803        },
804        $self,
805        $self->resize_manager,
806        $self->resize_thread_queue
807    );
808
809}
810
811
812sub init_resize_done_event_handler {
813    my ( $self ) = @_;
814
815    Wx::Event::EVT_COMMAND(
816        $self,
817        -1,
818        $resize_done_event,
819        sub {
820            my ( $handler, $event ) = @_;
821            my $data : shared = shared_clone($event->GetData);
822            $handler->on_image_progress($data);
823            # image is prepared, send to transfer queue
824            $self->transfer_thread_queue->enqueue($data);
825        } 
826    );
827
828}
829
830
831sub init_resize_progress_event_handler {
832    my ( $self ) = @_;
833
834    Wx::Event::EVT_COMMAND(
835        $self,
836        -1,
837        $resize_progress_event,
838        sub {
839            my ( $handler, $event ) = @_;
840            my $data : shared = shared_clone($event->GetData);
841            $handler->on_image_progress($data);
842        } 
843    );
844
845}
846
847
848sub on_image_progress {
849    my ( $self, $image ) = @_;
850
851    Wx::PostEvent(
852        $self->frame,
853        Wx::PlThreadEvent->new(
854            -1,
855            $self->image_progress_event,
856            $image
857        )
858    );
859}
860
861
862sub init_images {
863    my ( $self ) = @_;
864
865    $self->images_version('0.1');
866
867    my $stored = $self->retrieve_from_file(
868        $self->images_file,
869        $self->images_version,
870    );
871
872    $self->images(
873        $stored ?
874        Uploader::Images->new(
875            $stored
876        ):
877        Uploader::Images->new(
878            {
879                storable_file => $self->images_file,
880                version       => $self->images_version,
881            }
882        )
883    );
884
885    $self->images->eng_caption_patterns(
886        $self->eng_caption_patterns
887    );
888
889    $self->images->create_wx_thumbnail_cbk(
890        sub { $self->resize_manager->create_wx_thumbnail(@_) }
891    );
892
893    $self->images->default_caption_cbk(
894        sub { $self->preferences->default_caption(@_) }
895    );
896
897    $self->images->default_caption_pattern_cbk(
898        sub { $self->preferences->default_caption_pattern(@_) }
899    );
900
901    $self->images->set_current_image(-1);
902}
903
904
905sub is_single_instance_server {
906    my ( $self ) = @_;
907
908    if($self->is_single_instance_running){
909        printf("connect_to_single_instance_server\n");
910        $self->connect_to_single_instance_server;
911        printf("connected to single_instance_server\n");
912        return 0;
913    }
914    else{
915        printf("start_single_instance_server\n");
916        $self->start_single_instance_server;
917        printf("single_instance_server started\n");
918        return 1;
919    }
920}
921
922
923sub is_single_instance_running {
924    my ( $self ) = @_;
925
926    $self->{_instance_checker}->IsAnotherRunning;
927} 
928
929
930my $single_instance_port = 9101;
931
932
933sub connect_to_single_instance_server {
934    my ( $self ) = @_;
935
936    my $socket = IO::Socket::INET->new(
937        PeerAddr => '127.0.0.1',
938        PeerPort => $single_instance_port,
939        Proto    => 'tcp',
940        Type     => IO::Socket::SOCK_STREAM(),
941    );
942
943    # if connection successful, server sends pid
944    if ($socket) {
945        my $pid = '';
946        my $read = $socket->sysread( $pid, 10 );
947        if ( defined $read and $read == 10 ) {
948            my $files = join(',', @{$self->argv});
949            printf("send to server %s\n", $files);
950            $socket->print("open $files\n");
951            #$socket->print("focus\n");
952            $socket->close;
953            return 0;
954        }
955    }
956}
957
958
959sub start_single_instance_server {
960    my ( $self ) =@_;
961
962    $self->{_single_instance_server} = Wx::SocketServer->new(
963        '127.0.0.1' => $single_instance_port,
964        Wx::wxSOCKET_NOWAIT Wx::wxSOCKET_REUSEADDR,
965    );
966
967    if( $self->{_single_instance_server}->Ok ) {
968        Wx::Event::EVT_SOCKET_CONNECTION(
969            $self,
970            $self->{_single_instance_server},
971            sub {
972                $self->single_instance_connect( $_[0] );
973            }
974        );
975    }
976    else {
977        delete $self->{_single_instance_server};
978        warn( Wx::gettext("Failed to create single instance server") );
979    }
980
981    return 1;
982
983}
984
985
986sub single_instance_connect {
987    my $self   = shift;
988    my $server = shift;
989    my $client = $server->Accept(0);
990
991    # Before we start accepting input,
992    # send the client our process ID.
993    $client->Write( sprintf( '% 10s', $$ ), 10 );
994
995    # Set up the socket hooks
996    Wx::Event::EVT_SOCKET_INPUT(
997        $self, $client,
998        sub {
999
1000            # Accept the data and stream commands
1001            my $command = '';
1002            my $buffer  = '';
1003            while ( $_[0]->Read( $buffer, 128 ) ) {
1004                $command .= $buffer;
1005                while ( $command =~ s/^(.*?)[\012\015]+//s ) {
1006                    $_[1]->single_instance_command( "$1", $_[0] );
1007                }
1008            }
1009            return 1;
1010        }
1011    );
1012    Wx::Event::EVT_SOCKET_LOST(
1013        $self, $client,
1014        sub {
1015            $_[0]->Destroy;
1016        }
1017    );
1018
1019    return 1;
1020}
1021
1022sub single_instance_command {
1023    my( $self, $line, $socket ) = @_;
1024
1025    return 1 unless defined $line && length $line;
1026
1027    # ignore the line if command isn't plain ascii
1028    return 1 unless $line =~ s/^(\S+)\s*//s;
1029
1030    if ( $1 eq 'open' ) {
1031        if ( -f $line ) {
1032            printf("received from incoming connection %s\n", $line);
1033            $self->images->add_images(
1034                [
1035                    grep { -f $_} split(/,/, $line)
1036                ]
1037            );
1038        }
1039    } 
1040    else {
1041        warn("Unsupported command '$1'");
1042    }
1043
1044    return 1;
1045}
1046
1047
1048sub stop_single_instance_server {
1049    my ( $self ) =@_;
1050
1051    if(exists $self->{_single_instance_server}){
1052        $self->{_single_instance_server}->Destroy;
1053        delete $self->{_single_instance_server};
1054    }
1055}
1056
1057
1058sub default_app {
1059    my ( $self ) = @_ ;
1060
1061    my $app = {
1062        site_thumb_dir   => $self->thumb_dir,
1063        wx_thumb_dir     => $self->wx_thumb_dir,
1064        site_resized_dir => $self->resized_dir,
1065        userdata_dir     => $self->userdata_dir,
1066        version          => $self->version,
1067        storable_file    => $self->app_file,
1068
1069    };
1070
1071    $app;
1072}
1073
1074
1075sub connect_or_exit {
1076    my ( $self ) = @_;
1077
1078
1079    my $not_exit = $self->login();
1080    $self->connection->Store;
1081    # user pressed OK
1082    if($not_exit){
1083        if( $self->use_connected ){
1084            while( $not_exit and $self->not_connected ){
1085                $not_exit = $self->login;
1086                last if $self->use_offline;
1087            }
1088            $self->init_transfer_manager;
1089        }
1090    }
1091    else {
1092        $self->stop_resize_manager;
1093        $self->stop_single_instance_server;
1094    }
1095
1096    $not_exit;
1097}
1098
1099
1100sub login {
1101    my ( $self ) = @_;   
1102
1103    $self->init_connection;
1104
1105    $self->login_dlg( 
1106        Uploader::GUI::LoginDlg->new(
1107            {
1108                title         => gettext("Piwigo login"),
1109                site_url      => sub { $self->connection->site_url(@_) },
1110                site_username => sub { $self->connection->site_username(@_) },   
1111                site_password => sub { $self->connection->site_password(@_) },
1112                use_offline   => sub { $self->connection->use_offline(@_) },   
1113            }
1114        )
1115    ) unless $self->login_dlg;
1116
1117    my $icon = Wx::Icon->new();
1118    $icon->LoadFile(
1119        $self->resource_path('favicon.ico'), 
1120        wxBITMAP_TYPE_ICO
1121    );
1122
1123    $self->login_dlg->SetIcon($icon);
1124
1125   
1126    my $rval = $self->login_dlg->ShowModal();
1127    $self->login_dlg->Show(0);
1128
1129    $self->init_branding;
1130   
1131    if ($self->connection->site_url !~ /^http:/){
1132        $self->connection->site_url(
1133            sprintf(
1134                "http://%s",
1135                $self->connection->site_url
1136            )
1137        );   
1138    }
1139
1140    $self->pwg(
1141        # get these parameters from dialog or from file
1142        Uploader::PWG->new(
1143            {
1144                site_url       => $self->connection->site_url,
1145                site_username  => $self->connection->site_username,
1146                site_password  => $self->connection->site_password,
1147                http_username  => $self->connection->http_username,
1148                http_password  => $self->connection->http_password,
1149                branding       => $self->branding,
1150                use_offline    => $self->use_offline,
1151                version        => $self->version,
1152            }
1153        )
1154    );
1155
1156    $rval;
1157}
1158
1159
1160# helper method to get the full path for a resource
1161sub resource_path{
1162    my ( $self, $file ) = @_;
1163
1164    File::Spec->catfile($self->root_dir, $self->resource_dir, $file);
1165}
1166
1167sub bin_path{
1168    my ( $self, $file ) = @_;
1169
1170    File::Spec->catfile($self->root_dir, $self->bin_dir, $file);
1171}
1172
1173sub locale_path{
1174    my ( $self, $file ) = @_;
1175
1176    File::Spec->catfile($self->root_dir, $self->locale_dir, $file);
1177}
1178
1179sub locale_catalog_path{
1180    my ( $self, $file ) = @_;
1181
1182    File::Spec->catfile($self->root_dir, $self->locale_dir, $self->locale->GetCanonicalName, $file);
1183}
1184
1185
1186# some labels differ with branding ( piwigo.com or piwigo.org )
1187sub init_branding {
1188    my ( $self ) =@_;
1189   
1190    if( $self->connection->site_url =~ /\.piwigo\.com/ ){
1191        $self->branding(
1192            {
1193                category  => gettext("album"),
1194                Category  => gettext("Album"),
1195                categories => gettext("albums"),
1196                Categories => gettext("Albums"),
1197                'Add new category' => gettext("Add new album"),
1198                'Category name' => gettext("Album name :"),
1199                'New category' => gettext("New album"),
1200                'What is the destination category?' => gettext("What is the destination album?")
1201            }
1202        );
1203    }
1204    else{
1205        $self->branding(
1206            {
1207                category  => gettext("categorie"),   
1208                Category  => gettext("Categorie"),   
1209                categories => gettext("categories"),   
1210                Categories => gettext("Categories"),   
1211                'Add new category' => gettext("Add new category"),
1212                'Category name' => gettext("Category name :"),
1213                'New category' => gettext("New category"),
1214                'What is the destination category?' => gettext("What is the destination category?")
1215            }
1216        );
1217    }   
1218}
1219
1220
1221sub use_connected {
1222    my ( $self ) = @_;
1223
1224    !$self->use_offline
1225}
1226
1227
1228sub not_connected {
1229    my ( $self ) = @_;
1230
1231    !$self->is_connected;
1232}
1233
1234
1235sub is_connected {
1236    my ( $self ) = @_;
1237
1238    my $_is_connected;
1239
1240    if($self->pwg->login_result->{stat} eq 'ok'){
1241        $_is_connected = 1;
1242    }
1243    else{
1244        Wx::MessageBox( 
1245            sprintf(
1246                "%s\n\n%s %s %s",
1247                $self->pwg->login_result->{message},
1248                gettext("Connection to"),
1249                $self->connection->site_url,
1250                gettext("failed"),
1251            ),
1252            gettext("Piwigo login error"),
1253            wxOK | wxICON_EXCLAMATION, 
1254        );
1255    }
1256   
1257    $_is_connected;
1258}
1259
1260
1261sub init_transfer_manager {
1262    my ( $self ) = @_;
1263
1264    $self->check_upload;
1265
1266    $self->transfer_manager(
1267        Uploader::TransferManager->new({
1268            site_thumb_dir   => $self->thumb_dir,
1269            site_resized_dir => $self->resized_dir,
1270            pwg              => $self->pwg,
1271        })
1272    );
1273
1274    $self->image_progress_event(
1275        $image_progress_event
1276    );
1277    $self->init_transfer_thread_queue;
1278    $self->init_transfer_done_event_handler;
1279    $self->init_transfer_progress_event_handler;
1280
1281}
1282
1283
1284sub check_upload {
1285    my ( $self ) = @_;
1286
1287    my $err_msg = $self->pwg->check_upload;
1288
1289    $err_msg = gettext("Your user account is not granted to upload photos") if
1290        'Access denied' eq $err_msg;
1291
1292    #Wx::LogMessage("%s", $err_msg) if $err_msg;
1293    Wx::MessageBox($err_msg, "", wxOK | wxICON_ERROR) if $err_msg;
1294
1295    $err_msg;
1296}
1297
1298
1299sub init_transfer_thread_queue {
1300    my ( $self ) = @_;
1301
1302    $self->transfer_thread_queue(
1303        Thread::Queue->new()
1304    );
1305
1306    # transfer worker starts when receiving data from the queue
1307    # transfer data must be sent to the calling $handler because $handler data
1308    # cannot be updated inside the thread
1309    $self->transfer_thread(
1310        $self->new_transfer_thread
1311    );
1312
1313}
1314
1315
1316sub new_transfer_thread {
1317    my ( $self ) = @_;
1318
1319    threads->create( 
1320        sub {
1321            my ( $handler, $transfer_manager, $queue ) = @_;
1322            while (my $data = $queue->dequeue ) {
1323                return 1 if 'STOP' eq $data;
1324                if('CANCEL' eq $data){
1325                    while($queue->pending){
1326                        $transfer_manager->cancel_image(
1327                            $queue->dequeue
1328                        );
1329                    }
1330                }
1331                else{
1332                    $transfer_manager->process_image(
1333                        $handler,
1334                        $transfer_progress_event,
1335                        $transfer_done_event,
1336                        $data
1337                    );
1338                }
1339            }
1340            printf("transfer queue %s\n", $queue->pending);
1341        },
1342        $self,
1343        $self->transfer_manager,
1344        $self->transfer_thread_queue
1345    );
1346
1347}
1348
1349
1350sub init_transfer_done_event_handler {
1351    my ( $self ) = @_;
1352
1353    Wx::Event::EVT_COMMAND(
1354        $self,
1355        -1,
1356        $transfer_done_event,
1357        sub {
1358            my ( $handler, $event ) = @_;
1359            my $data = $event->GetData;
1360            $handler->on_image_progress($data);
1361        } 
1362    );
1363
1364}
1365
1366
1367sub init_transfer_progress_event_handler {
1368    my ( $self ) = @_;
1369
1370    Wx::Event::EVT_COMMAND(
1371        $self,
1372        -1,
1373        $transfer_progress_event,
1374        sub {
1375            my ( $handler, $event ) = @_;
1376            my $data : shared = shared_clone($event->GetData);
1377            $handler->on_image_progress($data);
1378        } 
1379    );
1380
1381}
1382
1383
1384sub start_transfer {
1385    my( $self, $images ) = @_;
1386   
1387    my $data : shared = shared_clone($images);
1388
1389    $self->transfer_thread_queue->enqueue($data);
1390}
1391
1392
1393sub start_resize {
1394    my( $self, $all_images ) = @_;
1395
1396    # we need to copy data from object to use another thread
1397    my $images = $self->images->get_images(
1398        $self->preferences->get_data,
1399        $self->transfer_manager->destination_category,
1400        $all_images
1401    );
1402    printf("resize thread %s\n", $self->resize_thread);
1403    $self->resize_thread_queue->enqueue(
1404        map { shared_clone($_) }@$images
1405    );
1406
1407    Wx::PostEvent(
1408        $self->frame,
1409        Wx::PlThreadEvent->new(
1410            -1,
1411            $self->resize_start_event,
1412            shared_clone($images)
1413        )
1414    );
1415
1416    $images;
1417}
1418
1419
1420sub stop_resize {
1421    my( $self ) = @_;
1422   
1423    my $data : shared = "STOP";
1424
1425    $self->resize_thread_queue->insert(0,$data);
1426}
1427
1428
1429sub cancel_resize {
1430    my( $self ) = @_;
1431   
1432    my $data : shared = "CANCEL";
1433
1434    $self->resize_thread_queue->insert(0,$data);
1435}
1436
1437
1438sub SetFrame {
1439    my ( $self, $frame ) = @_;   
1440
1441    my $url = $self->connection->site_url;
1442   
1443    if($self->use_offline){
1444        $url = gettext("Work Offline");
1445    }
1446
1447    $self->frame($frame);
1448
1449    my $icon = Wx::Icon->new();
1450    $icon->LoadFile(
1451        File::Spec->catfile(
1452            $self->root_dir, $self->resource_dir, 'favicon.ico'
1453        ), 
1454        wxBITMAP_TYPE_ICO
1455    );
1456    $self->frame->SetIcon($icon);
1457}
1458
1459
1460sub GetWxBitmapType {
1461    my ( $self, $type ) = @_;
1462   
1463    $self->{IMGTYPE}->{$type};
1464}
1465
14661;
Note: See TracBrowser for help on using the repository browser.