#!/usr/bin/perl #################################################################Modules use strict; use warnings; use Cwd; use Fcntl; use threads; use Net::NNTP; use Date::Parse; use Date::Format; use threads::shared; use Convert::BulkDecoder; use MLDBM qw(DB_File Storable); $| = 1; ################################################################Log File open ('STDERR', '>', 'NewsSurfer.log') #<-todo, add clear log function and retain logs between launches or warn "Can not create a log file!\n$!"; ############################################################Declarations our $VERSION = 3.02; my ($mw, $windowx, $windowy, %shash, %threads, $DEBUG, $RSS,); #################################################################Threads warn "Launching thread\n"; foreach my $l qw ( rss article decode post list die return progress optionCSV ) { share($shash{1}{$l}); $shash{1}{$l} = 0; } $threads{1} = threads->new(\&worker, 1); warn "Thread 1 is active\n"; ########################################################Optional Modules if ($^O eq 'MSWin32') { eval { require Win32::Console; Win32::Console::Free() }; if ($@) { warn "Win32::Console is not installed.\n$@"; } } #RSS Modules eval { require XML::FeedPP; require LWP::Simple; require Data::Dumper }; if (! $@) { $RSS = 1; } #############################################################GUI Modules use Tk::ResizeButton; use Tk::ProgressBar; use Tk::ItemStyle; use Tk::ROText; use Tk::HList; use Tk::Pod; use Tk; ####################################################################Main $DEBUG = $ARGV[0] || 0; $mw = MainWindow->new( -title => 'NewsSurfer', -relief => 'groove', -colormap => 'new', -bd => 2, ); splash(); news_gui(); Tk::MainLoop(); ####################################################################Exit foreach my $k (sort keys %threads) { warn "Destroying Thread [$k]\n"; $shash{$k}{'die'} = 1; sleep(1); } warn "Exiting..\n"; close STDERR; exit; #############################################################Subroutines sub splash #------------------------------------------------------------ { $mw->gridPropagate(0); $mw->withdraw; my ($image, $splash, $canvas,); $windowx = $mw->screenwidth; $windowy = $mw->screenheight; my $x = ($windowx - 500) / 2; my $y = ($windowy - 288) / 2; my $geometry = '500x288+'.$x.'+'.$y; { my $imagedata = load_image(1); $image = $mw->Photo( -format => 'gif', -data => $imagedata ); } $splash = $mw->Toplevel(-takefocus => 1,); $splash->overrideredirect(1); $splash->geometry($geometry); $splash->resizable(0, 0); $canvas = $splash->Canvas()->pack( -fill => 'both', -expand => 1, ); $canvas->createImage(0,0, -image => $image, -anchor => 'nw', ); $splash->update; $mw->after(12000, sub { $splash->destroy; $mw->GeometryRequest($windowx,$windowy); $mw->Post(-5,-5); $mw->update; }); return (1); } sub news_gui #---------------------------------------------------------- { #Widget Initialization our $sort_cnt = 3; our $msglimitOption; my $sys_bg = $mw->cget(-background); my $sys_fg = $mw->cget(-foreground); dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; unless ($OPT{Mail}) { $OPT{Mail} = 'NewsSurfer@domain.invalid'; } unless ($OPT{DDir}) { $OPT{DDir} = '.'; } unless (-e $OPT{DDir} and -d $OPT{DDir}) { $OPT{DDir} = '.'; } dbmclose %OPT; $mw->gridRowconfigure(2, -weight => 1,); $mw->gridRowconfigure(4, -minsize => 8,); $mw->gridColumnconfigure(1, -weight => 1,); $mw->setPalette( background => '#a1a1a1', activebackground => '#a1a1a1', activeforeground => '#000fff', ); #create frames and panedwindow #my($pw1, $f1_main, $f2_main, $f3_main, $f2_tool,); my($pw1, $f1_main, $f2_main, $f3_main,); { $f1_main = $mw->Frame( -relief => 'flat', -bd => 2, )->grid( -in => $mw, -column => '1', -padx => '8', -sticky => 'news', -row => '1', -pady => '0', ); $f1_main->gridColumnconfigure(2, -minsize => 8,); $f1_main->gridColumnconfigure(7, -minsize => 8,); $f1_main->gridColumnconfigure(11, -minsize => 8,); $f1_main->gridColumnconfigure(14, -weight => 1,); $f2_main = $mw->Frame( -relief => 'groove', -bd => 4, )->grid( -in => $mw, -column => '1', -padx => '8', -sticky => 'news', -row => '2', -pady => '0', ); $f2_main->gridRowconfigure(1, -weight => 1,); $f2_main->gridColumnconfigure(1, -weight => 1,); $f3_main = $mw->Frame( -relief => 'groove', -bd => 2, )->grid( -in => $mw, -column => '1', -padx => '8', -sticky => 'news', -row => '3', -pady => '0', ); $f3_main->gridRowconfigure(1, -weight => 1,); $f3_main->gridColumnconfigure(1, -weight => 1,); $pw1 = $f2_main->Panedwindow( -orient => 'vertical', -relief => 'groove', -bd => 2, )->grid( -in => $f2_main, -row => '1', -sticky => 'news', -column => '1', ); } #frame 1 (toolbar frame) my $logo; our @buttons; { $logo = $mw->Label( -text => 'NewsSurfer', -font => '{Courier New} 16', )->grid( -in => $f1_main, -column => '14', -sticky => 'news', -row => '1', ); my $c = 1; foreach my $l qw( scan grab read_message post browse group opt show_log help quit ) { my $sub = \&{ $l; }; my $w = $f1_main->Button( -activebackground => '#a1a1a1', -bd => 0, -highlightthickness => 0, -command => sub { &$sub(); }, )->grid( -in => $f1_main, -column => $c, -sticky => 'news', -row => '1', ); push (@buttons, $w); if ($c =~ m/^(1|6|10)$/) { $c++; } $c++; } } #frame 2 (main display frame) #upper Function select our $lb1_grp; { $lb1_grp = $pw1->Scrolled( 'HList', -highlightthickness => 1, -columns => 3, -header => 1, -height => 3, -indicator => 1, -indicatorcmd => sub {}, -font => '{Ariel} 8', -highlightcolor => '#000000', -scrollbars => 'ose', -background => '#ffffff', -foreground => '#000000', -selectbackground => '#000000', -selectforeground => '#fff000', -selectmode => 'single', ); my $c = 0; foreach my $label ('Group', 'Last Scanned', '',) { my $w = $lb1_grp->ResizeButton( -widget => \$lb1_grp, -column => $c, -text => "$label", -font => '{Ariel} 8', -activebackground => '#a1a1a1', -activeforeground => '#000000', -relief => 'flat', -anchor => 'w', -borderwidth => 0, -takefocus => 0, -command => sub {}, ); $lb1_grp->columnWidth($c, -char => '1'); $lb1_grp->header( 'create', $c, -itemtype => 'window', -headerbackground => '#a1a1a1', -widget => $w, -borderwidth => 1, ); $c++; } { my ($c, @colWidth,); if ($windowx >= 800) { @colWidth = (127, 25, ''); } else { @colWidth = (95, 25, ''); } $c = 0; foreach my $width (@colWidth) { $lb1_grp->columnWidth ($c, -char => $width); $c++; } } } #lower HList our($lb2_msg,); { $lb2_msg = $pw1->Scrolled( 'HList', -highlightthickness => 1, -columns => 6, -header => 1, -indicator => 1, -indicatorcmd => sub {}, -separator => '^', -scrollbars => 'ose', -highlightcolor => '#000000', -background => '#ffffff', -foreground => '#000000', -selectbackground => '#000000', -selectforeground => '#fff000', -selectmode => 'extended', ); #lower HList column headers my $c = 0; foreach my $label ( 'Headers', 'From', 'Parts', 'Bytes', 'Date', ' ', ) { my $w = $lb2_msg->ResizeButton( -widget => \$lb2_msg, -column => $c, -text => $label, -font => '{Ariel} 8', -activebackground => '#a1a1a1', -activeforeground => '#000fff', -relief => 'flat', -anchor => 'w', -borderwidth => 0, -takefocus => 0, -command => sub { lb2_msg_sort("$label") },); $lb2_msg->columnWidth($c, -char => '1'); $lb2_msg->header( 'create', $c, -itemtype => 'window', -headerbackground => '#a1a1a1', -widget => $w, -borderwidth => 1, ); $c++; } if ($windowx >= 800) { $lb2_msg->columnWidth (0, -char => '100'); } else { $lb2_msg->columnWidth (0, -char => '68'); } $lb2_msg->columnWidth (1, -char => '10'); $lb2_msg->columnWidth (2, -char => '7'); $lb2_msg->columnWidth (3, -char => '10'); $lb2_msg->columnWidth (4, -char => '25'); $lb2_msg->columnWidth (5, -char => ''); } #frame 3 ('Statusbar frame') our($sb_lab,); our $sblabel = ' '; our $pb = 0; { $sb_lab = $mw->Label( -text => " $sblabel", -anchor => 'w', -relief => 'sunken', -bd => 2, )->grid( -in => $f3_main, -column => '1', -sticky => 'ew', -row => '1', ); $mw->ProgressBar( -relief => 'sunken', -bd => 2, -length => 270, -from => 0, -to => 100, -blocks => 50, -colors => [0, 'green'], -variable => \$pb, )->grid( -in => $f3_main, -column => '2', -sticky => 'news', -row => '1', ); } #log window our($tl1, $txt_log,); { $tl1 = $mw->Toplevel( -title => 'View Log', -relief => 'groove', -bd => 2, ); $tl1->gridRowconfigure(1, -minsize => 8, -weight => 1,); $tl1->gridColumnconfigure(1, -minsize => 8, -weight => 1,); $tl1->transient($mw); $tl1->withdraw; my $f = $tl1->Frame( -relief => 'sunken', -bd => 2, )->grid( -in => $tl1, -columnspan => '2', -column => '1', -rowspan => '1', -row => '1', -sticky => 'news' ); $f->gridRowconfigure(1, -minsize => 8, -weight => 1,); $f->gridColumnconfigure(1, -minsize => 8, -weight => 1,); $txt_log = $f->Scrolled( 'ROText', -scrollbars => 'se', -foreground => '#ffffff', -background => '#000000', -selectforeground => '#fff000', -selectbackground => '#000000', -wrap => 'none', -relief => 'flat', -bd => 0, -width => 80, -height => 30, )->grid( -in => $f, -columnspan => '1', -column => '1', -rowspan => '1', -row => '1', -sticky => 'news' ); $txt_log->tagConfigure('Red', -foreground => '#ff0000'); $txt_log->tagConfigure('Blue', -foreground => '#000fff'); $txt_log->tagConfigure('Yellow', -foreground => '#fff000'); { my $menu = $txt_log->menu; $menu->configure( -bg => $sys_bg, -fg => $sys_fg, -activeforeground => '#000fff', -activebackground => '#a1a1a1', ); $menu->delete('File'); $menu->delete('Search'); $menu->delete('View'); } my $c = 1; foreach my $label ('C l o s e', 'S a v e',) { my $s = 'w'; if ($c > 1) { $s = 'e' }; my $sublabel = $label; $sublabel =~ s/\s//g; my $sub = \&{ 'log_'.lc($sublabel); }; my $w = $tl1->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { &$sub(); }, )->grid( -in => $tl1, -column => $c, -sticky => $s, -row => '4', ); FlashButton($w, '#181830', $sys_fg); $c++; } } #post message window our($tl2, $txt_post, @post_entries,); { $tl2 = $mw->Toplevel( -relief => 'groove', -bd => 2, ); $tl2->title('Post Message'); $tl2->resizable(0, 0); $tl2->transient($mw); $tl2->withdraw; $tl2->gridColumnconfigure(3, -weight => 1,); my $c = 1; foreach my $label ('From: ', 'Subject: ',) { $tl2->Label( -text => $label, )->grid( -in => $tl2, -column => '1', -sticky => 'e', -row => $c, ); my $w = $tl2->Entry( -width => 60, -background => '#ffffff', -foreground => '#000000', )->grid( -in => $tl2, -columnspan => '2', -column => '2', -rowspan => '1', -row => $c, -sticky => 'w' ); $c++; push (@post_entries, $w); } my $f = $tl2->Frame( -relief => 'sunken', -bd => 2, )->grid( -in => $tl2, -columnspan => '4', -column => '1', -rowspan => '1', -row => '3', -sticky => 'nws' ); $txt_post = $f->Scrolled( 'Text', -scrollbars => 'se', -background => '#ffffff', -foreground => '#000000', -selectforeground => '#fff000', -selectbackground => '#000000', -relief => 'flat', -wrap => 'none', -height => 30, -width => 80, )->grid( -in => $f, -column => '1', -sticky => 'nws', -row => '1', ); { my $post_menu = $txt_post->menu; $post_menu->delete('File'); $post_menu->delete('Search'); $post_menu->delete('View'); } $c = 1; foreach my $label ( 'C a n c e l ', 'P o s t ', 'A t t a c h a n d P o s t', ) { my $sublabel = $label; $sublabel =~ s/\s//g; my $sub = \&{ 'post_'.lc($sublabel); }; my $w = $tl2->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { &$sub(); }, )->grid( -in => $tl2, -column => $c, -sticky => 'w', -row => '4', ); FlashButton($w, '#181830', $sys_fg); $c++; } } #groups subscription window our ($tl3, $lb_grp, $grpSubSearch,); { $tl3 = $mw->Toplevel( -relief => 'groove', -bd => 2, ); $tl3->title('Groups'); $tl3->geometry("+90+35"); $tl3->resizable(0, 0); $tl3->transient($mw); $tl3->withdraw; $tl3->gridColumnconfigure(5, -weight => 1,); my $f = $tl3->Frame( -relief => 'sunken', -bd => 2, )->grid( -in => $tl3, -columnspan => '5', -column => '1', -rowspan => '1', -row => '2', -sticky => 'news' ); $lb_grp = $f->Scrolled( 'HList', -scrollbars => 'ose', -background => '#000000', -foreground => '#ffffff', -selectbackground => '#fff000', -selectforeground => '#000000', -highlightthickness => 0, -selectmode => 'extended', -relief => 'flat', -takefocus => 0, -header => 1, -columns => 2, -indicator => 1, -height => 30, -width => 96, -indicatorcmd => sub {}, #do nothing )->grid( -in => $f, -column => '1', -sticky => 'news', -row => '1', ); $lb_grp ->columnWidth (0, -char => '70'); $lb_grp ->columnWidth (1, -char => '20'); $lb_grp ->headerCreate(0, -text => "Newsgroups",); $lb_grp ->headerCreate(1, -text => "Message Count",); $f = $tl3->Frame( -relief => 'flat', )->grid( -in => $tl3, -column => '1', -columnspan => '5', -sticky => 'ew', -row => '1', -rowspan => '1', ); $f->gridColumnconfigure(3, -weight => 1,); my $e = $tl3->Entry( -background => '#ffffff', -foreground => '#000000', -textvariable => \$grpSubSearch, -width => 40, )->grid( -in => $f, -column => '1', -sticky => 'ew', -row => '1', ); $e->bind('' => sub { search($lb_grp, 'GROUP', 0); }); my $c = 2; foreach my $label ( 'S e a r c h', 'S h o w A l l N e w s g r o u p s' ) { my ($w, $showAll,); if ($c > 2) { $showAll = 1; } else { $showAll = 0; } $w = $f->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { search($lb_grp, 'GROUP', $showAll); } )->grid( -in => $f, -column => $c, -sticky => 'ew', -row => '1', ); FlashButton($w, '#181830', $sys_fg); $c += 2; } $c = 1; foreach my $label ( 'C l o s e ', 'U p d a t e ', 'S u b s c r i b e ', 'U n S u b s c r i b e', ) { my $sublabel = $label; $sublabel =~ s/\s//g; my $sub = \&{ 'grp_' . lc($sublabel); }; my $w = $tl3->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { &$sub(); }, )->grid( -in => $tl3, -column => $c, -sticky => 'n', -row => '3', ); FlashButton($w, '#181830', $sys_fg); $c++; } } #read message window our($tl4, $txt_read,); { $tl4 = $mw->Toplevel( -relief => 'groove', -bd => 2, ); $tl4->title('Read Message'); $tl4->transient($mw); $tl4->withdraw; my $f = $tl4->Frame( -relief => 'sunken', -bd => 2, )->grid( -in => $tl4, -columnspan => '2', -column => '1', -rowspan => '1', -row => '1', -sticky => 'news' ); $txt_read = $f->Scrolled( 'ROText', -scrollbars => 'se', -background => '#ffffff', -foreground => '#000000', -selectforeground => '#fff000', -selectbackground => '#000000', -wrap => 'none', -relief => 'flat', -width => 80, -height => 30, )->grid( -in => $f, -columnspan => '1', -column => '1', -rowspan => '1', -row => '1', -sticky => 'news' ); { my $read_menu = $txt_read->menu; $read_menu->configure( -bg => $sys_bg, -fg => $sys_fg, -activeforeground => '#000fff', -activebackground => '#a1a1a1', ); $read_menu->delete('File'); $read_menu->delete('Search'); $read_menu->delete('View'); } my $c = 1; foreach my $label ('C l o s e', 'R e p l y',) { my $s = 'w'; if ($c > 1) { $s = 'e' }; my $sublabel = $label; $sublabel =~ s/ \s+ #remove any whitespace in the string //xg; my $sub = \&{ 'read_'.lc($sublabel); }; my $w = $tl4->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { &$sub(); }, )->grid( -in => $tl4, -column => $c, -sticky => $s, -row => '4', ); FlashButton($w, '#181830', $sys_fg); $c++; } } #options window our($tl5, $quoteSig, @OPT_widgets,); { $tl5 = $mw->Toplevel( -relief => 'groove', -bd => 2, -takefocus => 1, ); $tl5->title('Options'); $tl5->geometry("+105+70"); $tl5->resizable(0, 0); $tl5->transient($mw); $tl5->withdraw; my $f = $tl5->Frame( -bd => 3, -relief => 'sunken', -bg => '#000000', )->grid( -in => $tl5, -column => '1', -sticky => 'e', -row => '1', ); $f->gridRowconfigure(5, -minsize => 8,); $f->gridRowconfigure(9, -minsize => 96, -weight => 1,); $f->gridColumnconfigure(3, -minsize => 96,); my $c = 1; foreach my $label ('NNTP Server: ', 'Username: ', 'Password: ', 'Email: ',) { $f->Label( -text => $label, -background => '#000000', -foreground => '#ffffff', -anchor => 'e', -width => 15, )->grid( -in => $f, -column => '1', -sticky => 'e', -row => $c, ); $c++; } $c = 6; foreach my $label ('Download Dir: ', 'Signature File: ',) { $label =~ m/\b(\w+)\b/; my $sub = \&{ 'opt_' . lc($1); }; my $w = $f->Button( -text => $label, -anchor => 'e', -relief => 'flat', -bg => '#000000', -fg => '#ffffff', -activeforeground => '#fff000', -activebackground => '#000000', -width => 15, -command => sub { &$sub(); }, )->grid( -in => $f, -column => '1', -sticky => 'e', -row => $c, ); FlashButton($w, '#181830', $sys_fg); $c++; } undef $c; foreach my $row qw(1 2 3 4 6 7) { my $w = $f->Entry( -width => 32, -background => '#ffffff', -foreground => '#000000', )->grid( -in => $f, -column => '2', -sticky => 'w', -row => $row, ); if ($row >= 3) { if ($row == 3) { $w->configure(-show => '*',); } elsif ($row >= 6) { $w->configure(-width => 64,); } } push (@OPT_widgets, $w); } my $w; $w = $f->Checkbutton( -foreground => '#ffffff', -background => '#000000', -activeforeground => '#ffffff', -activebackground => '#000000', -selectcolor => '#000000', -variable => \$quoteSig, )->grid( -in => $f, -column => 1, -sticky => 'e', -row => 8, ); push (@OPT_widgets, $w); $w = $f->Label( -text => 'Quote signatures in reply message.', -background => '#000000', -foreground => '#ffffff', )->grid( -in => $f, -column => 2, -sticky => 'w', -row => 8, ); $w = $tl5->Button( -text => 'C l o s e & S a v e', -relief => 'flat', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -command => sub { opt_close(); } )->grid( -in => $tl5, -column => '1', -sticky => 'w', -row => '2', ); FlashButton($w, '#181830', $sys_fg); } #help window our($tl6, $txt_help,); { $tl6 = $mw->Toplevel( -relief => 'groove', -bd => 2, ); $tl6->title('Help'); $tl6->geometry("+93+70"); $tl6->resizable(0, 0); $tl6->transient($mw); $tl6->withdraw; $tl6->gridColumnconfigure(2, -weight => 1,); my $f = $tl6->Frame( -relief => 'sunken', -bd => 2, )->grid( -in => $tl6, -column => '1', -columnspan => '3', -sticky => 'news', -row => '1', ); $txt_help = $f->Scrolled( 'ROText', -scrollbars => 'oe', -background => '#000000', -foreground => '#ffffff', -selectbackground => '#000000', -selectforeground => '#fff000', -wrap => 'none', -relief => 'flat', -width => 80, -height => 20, )->grid( -in => $f, -column => '1', -sticky => 'news', -row => '1', ); $txt_help->menu(undef); my $c = 1; foreach my $label ('C l o s e', 'A b o u t',) { my $sublabel = $label; $sublabel =~ s/\s//g; my $sub = \&{ 'help_' . lc($sublabel); }; my $w = $tl6->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { &$sub(); } )->grid( -in => $tl6, -column => $c, -sticky => 'n', -row => '2', ); FlashButton($w, '#181830', $sys_fg); $c += 2; } } #group right click menu our($tl7,); { $tl7 = $mw->Toplevel( -title => 'group_rc_menu', -relief => 'raised', -borderwidth => 2.5, ); $tl7->overrideredirect(1); $tl7->resizable(0, 0); $tl7->transient($mw); $tl7->withdraw; my $f = $tl7->Frame( -relief => 'sunken', -bd => '1.5', -takefocus => '1', )->grid( -in => $tl7, -column => '1', -sticky => 'news', -row => '1', ); $f->gridRowconfigure(3, -minsize => 2,); $f->gridRowconfigure(5, -minsize => 2,); $f->bind('' => sub { $tl7->withdraw; }); $f->Frame( -relief => 'groove', -bd => 8, )->grid( -in => $f, -column => 1, -sticky => 'news', -row => 3, ); $f->Frame( -relief => 'groove', -bd => 2, )->grid( -in => $f, -column => 1, -sticky => 'news', -row => 5, ); $f->Button( -text => 'Scan', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { scan(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 1, ); $f->Button( -text => 'Load', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { message_load(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 2, ); $f->Button( -text => 'Search', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { search_popup(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 4, ); $f->Button( -text => 'Clear', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { message_clear(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 6, ); $f->Button( -text => 'Reset', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { $lb1_grp->focus; rset_cmd(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 7, ); $f->Button( -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -text => 'Remove', -anchor => 'w', -width => 10, -command => sub { $lb1_grp->focus; grp_unsubscribe('X'); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 8, ); } #message right click menu our($tl8,); { $tl8 = $mw->Toplevel( -title => 'message_rc_menu', -relief => 'raised', -borderwidth => 2.5, ); $tl8->overrideredirect(1); $tl8->resizable(0, 0); $tl8->transient($mw); $tl8->withdraw; my $f = $tl8->Frame( -relief => 'sunken', -bd => 1.5, -takefocus => 1, )->grid( -in => $tl8, -column => 1, -sticky => 'news', -row => 1, ); $f->bind('' => sub { $tl8->withdraw; }); $f->Button( -text => 'Read', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { read_message(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 1, ); $f->Button( -text => 'Grab', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { grab(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 2, ); $f->Button( -text => 'Grab&Open', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { grab(1); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 3, ); $f->Button( -text => 'SelectAll', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { lb2_msg_select_all(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 4, ); $f->Button( -text => 'Delete', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -anchor => 'w', -width => 10, -command => sub { $lb2_msg->focus; message_delete(); })->grid( -in => $f, -column => 1, -sticky => 'n', -row => 5, ); } #message download window our($tl9, $l1_msgs, $e1_msgs, $dlnew,); { $tl9 = $mw->Toplevel( -relief => 'groove', -bd => 2, ); $tl9->title('Download Messages'); $tl9->geometry("+220+160"); $tl9->resizable(0, 0); $tl9->transient($mw); $tl9->withdraw; $tl9->gridColumnconfigure(4, -minsize => 64, -weight => 1,); my $f = $tl9->Frame( -bg => '#000000', -relief => 'sunken', -bd => 3, )->grid( -in => $tl9, -columnspan => 4, -column => 1, -rowspan => 1, -row => 1, -sticky => 'w', ); $f->gridRowconfigure(4, -minsize => 100,); $f->gridColumnconfigure(4, -minsize => 32, -weight => 1,); $l1_msgs = $f->Label( -text => '', -bg => '#000000', -fg => '#ffffff', )->grid( -in => $f, -columnspan => 3, -column => 1, -rowspan => 1, -row => 1, -sticky => 'w', ); $f->Label( -text => 'Enter the number of messages to be downloaded: ', -bg => '#000000', -fg => '#ffffff', )->grid( -in => $f, -padx => 0, -columnspan => 2, -column => 1, -pady => 8, -row => 2, -sticky => 'w', ); $f->Label( -text => 'Newest messages only (mark the rest read).', -bg => '#000000', -fg => '#ffffff', )->grid( -in => $f, -padx => 0, -column => 2, -pady => 0, -row => 4, -sticky => 'nw', ); $e1_msgs = $f->Entry( -bg => '#ffffff', -fg => '#000000', -selectbackground => '#000000', -selectforeground => '#fff000', -width => 6, )->grid( -in => $f, -padx => 0, -column => 3, -pady => 8, -row => 2, -sticky => 'w', ); $f->Checkbutton( -variable => \$dlnew, -activeforeground => '#000000', -activebackground => '#000000', -foreground => '#000000', -background => '#000000', -selectcolor => '#ffffff', )->grid( -in => $f, -padx => '0', -column => '1', -padx => '0', -row => '4', -sticky => 'ne', ); my $c = 1; my $sticky = 'w'; foreach my $label ('O k', 'C a n c e l',) { my $sublabel = $label; $sublabel =~ s/\s//g; my $w = $tl9->Button( -text => $label, -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { $msglimitOption = uc($sublabel); }, )->grid( -in => $tl9, -column => $c, -sticky => 'e', -row => '2', ); FlashButton($w, '#181830', $sys_fg); $c += 3; } } #Search group popup our($tla,); { $tla = $mw->Toplevel(); $tla->title('Search Group'); $tla->geometry("+250+200"); $tla->resizable(0, 0); $tla->transient($mw); $tla->withdraw; $tla->gridColumnconfigure(1, -weight => 1,); my $f = $tla->Frame( -bd => 3, -relief => 'sunken', -bg => '#000000', )->grid( -in => $tla, -column => '1', -columnspan => '2', -sticky => 'news', -row => '1', -rowspan => '1', ); $tla->Label( -text => 'To clear previous search results, '. 'perform an empty search.'."\n\n\n\n", -bg => '#000000', -fg => '#ffffff', )->grid( -in => $f, -column => '1', -sticky => 'news', -row => '1', ); my $e = $tla->Entry( #$grpSubSearch is shared with the groups window -textvariable => \$grpSubSearch, -bg => '#ffffff', -fg => '#000000', -relief => 'sunken', -bd => 2, )->grid( -in => $tla, -column => '1', -sticky => 'news', -row => '2', ); $e->bind('' => sub { search($lb2_msg); }); my $w = $tla->Button( -text => 'S e a r c h', -activeforeground => '#000fff', -activebackground => '#a1a1a1', -relief => 'flat', -command => sub { search($lb2_msg); } )->grid( -in => $tla, -column => '2', -sticky => '', -row => '2', ); FlashButton($w, '#181830', $sys_fg); } #$pw1->add($f2_tool, $lb2_msg,); $pw1->add($lb1_grp, $lb2_msg,); #Bindings $tl2->protocol(WM_DELETE_WINDOW => \&post_cancel); $tl3->protocol(WM_DELETE_WINDOW => \&grp_close); $tl4->protocol(WM_DELETE_WINDOW => \&read_close); $tl9->protocol(WM_DELETE_WINDOW => \&dlmsgs_cancel); $tl1->protocol(WM_DELETE_WINDOW => sub {$tl1->withdraw;}); $tl5->protocol(WM_DELETE_WINDOW => sub {$tl5->withdraw;}); $tl6->protocol(WM_DELETE_WINDOW => sub {$tl6->withdraw;}); $tla->protocol(WM_DELETE_WINDOW => sub {$tla->withdraw;}); $logo->bind('' => sub { $logo->configure(-text=>'');$mw->update;$mw->after(200); $logo->configure(-text=>'N');$mw->update;$mw->after(180); $logo->configure(-text=>'Ne');$mw->update;$mw->after(130); $logo->configure(-text=>'New');$mw->update;$mw->after(120); $logo->configure(-text=>'News');$mw->update;$mw->after(160); $logo->configure(-text=>'NewsS');$mw->update;$mw->after(130); $logo->configure(-text=>'NewsSu');$mw->update;$mw->after(100); $logo->configure(-text=>'NewsSur');$mw->update;$mw->after(120); $logo->configure(-text=>'NewsSurf');$mw->update;$mw->after(130); $logo->configure(-text=>'NewsSurfe');$mw->update;$mw->after(110); $logo->configure(-text=>'NewsSurfer');$mw->update;$mw->after(100); }); $lb1_grp ->bind('' => sub { raise_rc_menu($tl7); }); $lb1_grp->bind(''=> sub { b4_grp_unsubscribe('X'); }); $lb2_msg ->bind('' => sub { my @sel = $lb2_msg->selectionGet; if ($sel[1]) { raise_rc_menu($tl8); } else { $lb2_msg->Tk::HList::ButtonRelease_1; raise_rc_menu($tl8, $lb2_msg); } }); $lb2_msg ->bind('' => sub { b2_grab_cmd(1); }); $lb2_msg->bind('' => \&read_message); $lb1_grp->bind('' => \&message_load); $lb1_grp->bind('' => \&scan); $lb1_grp->bind('' => \&message_clear); $lb1_grp->bind('' => \&message_clear); $lb1_grp->bind('' => \&rset_cmd); $lb1_grp->bind('' => \&rset_cmd); $lb2_msg->bind('' => \&message_delete); $lb2_msg->bind('' => \&read_message); $lb2_msg->bind('' => \&lb2_msg_select_all); $lb2_msg->bind('' => \&lb2_msg_select_all); $lb2_msg->bind('' => \&lb2_msg_select_end); $lb2_msg->bind('' => \&lb2_msg_select_hom); $lb2_msg->bind('' => \&message_delete); $lb2_msg->bind('' => \&message_delete); { my $c = 10; foreach my $b (@buttons) { #bind images onto buttons MainButtons($b, $c); $c++ } } #Defaults foreach my $p (glob 'part*.pt') { #todo - recovery unlink $_ || warn "Unable to delete part: [$p]\n$!"; } warn 'Warning - NewsSurfer has started. (' . localtime() . "}\n"; $sblabel = 'Ready'; $msglimitOption = 0; display_groups(); $lb1_grp->focus(); #Callbacks #sub toolBar #--------------------------------------------------------- #{ # #'Email', 'Newsgroups', 'RSS', # my $cmd = uc ($_[0]) || return (0); # my ($c, @lb1_headers,); # # if ($cmd eq 'NEWSGROUPS') { # #Newsgroup mode # @lb1_headers = ('Group', 'Last Scanned', '',); # #Display NNTP subscriptions # display_groups(); # $lb1_grp->focus(); # } # elsif ($cmd eq 'RSS') { # #RSS mode # $mw->Busy(-recurse => 1); # @lb1_headers = ('URL', 'Last Updated', '',); # #Display RSS URL's # display_rss(); # $mw->Unbusy; # } # else { # #Error # return (0); # } # $c = 0; # foreach my $label (@lb1_headers) { # my $w = $lb1_grp->ResizeButton( # -widget => \$lb1_grp, # -column => $c, # -text => "$label", # -font => '{Ariel} 8', # -activebackground => '#a1a1a1', # -activeforeground => '#000000', # -relief => 'flat', # -anchor => 'w', # -borderwidth => 0, # -takefocus => 0, # -command => sub {}, # ); # $lb1_grp->columnWidth($c, -char => '1'); # $lb1_grp->header( # 'create', $c, # -itemtype => 'window', # -headerbackground => '#a1a1a1', # -widget => $w, # -borderwidth => 1, # ); # $c++; # } # { # my ($c, @colWidth,); # if ($windowx >= 800) { # @colWidth = (115, 25, ''); # } # else { # @colWidth = (95, 25, ''); # } # $c = 0; # foreach my $width (@colWidth) { # $lb1_grp->columnWidth ($c, -char => $width); # $c++; # } # } # return (1); #} #sub display_rss #----------------------------------------------------- #{ # my ($URLs, $lb1_k1, $lb1_k2, $lb1_r1, $c,); # # $lb1_grp->delete('all'); # $lb1_k1 = $lb1_grp->ItemStyle('text', # -anchor => 'w', # -selectforeground => '#fff000', # -background => '#ffffff', # -foreground => '#000000', # -font => '{Arial} 8', # ); # $lb1_r1 = $lb1_grp->ItemStyle('text', # -anchor => 'w', # -selectforeground => '#fff000', # -background => '#ffffff', # -foreground => '#f01010', # -font => '{Arial} 8', # ); # $lb1_k2 = $lb1_grp->ItemStyle('text', # -anchor => 'e', # -selectforeground => '#fff000', # -background => '#ffffff', # -foreground => '#000000', # -font => '{Arial} 8', # ); # # open (FH, '<', 'NewsSurfer_RSS.ini') # or die "Cannot open NewsSurfer_RSS.ini\n$!"; # while (my $url = ()) { # if ($url =~ m/^$/ || $url =~ m/^#/) { # next; # } # else { # chomp ($url); # $c++; # } # $lb1_grp->add($c); # if (fetchRSS($url)) { # $lb1_grp->itemCreate($c, 0, # -text => $url, # -style => $lb1_k1, # ); # } # else { # $lb1_grp->itemCreate($c, 0, # -text => $url . ' (unreachable)', # -style => $lb1_r1, # ); # } # $lb1_grp->itemCreate($c, 1, # -text => 'todo', # -style => $lb1_k2, # ); # $mw->update; # } # close FH or die "Cannot close NewsSurfer_RSS.ini\n$!"; # return (1); #} #sub fetchRSS #-------------------------------------------------------- #{ # my $url = $_[0] || return (0); # my ($xml, $rssContent,); # my $lb2_b1 = $lb2_msg->ItemStyle('text', # -anchor => 'e', # -selectforeground => '#fff000', # -background => '#ffffff', # -foreground => '#0000ff', # -font => '{Arial} 8', # ); # my $lb2_b2 = $lb2_msg->ItemStyle('text', # -anchor => 'w', # -selectforeground => '#fff000', # -background => '#ffffff', # -foreground => '#0000ff', # -font => '{Arial} 8', # ); # # #Get RSS URL # if ($rssContent = get($url)) { # #Parse RSS # eval { $xml = XML::FeedPP->new($rssContent) }; # if (! $@) { # my (%file, $tmp, $c,); # tie %file, 'RSSDB', "rssfile", O_CREAT|O_RDWR, '0640' # or error('RSSDB', 'DIE',); # # #Save RSS header to DB # $tmp = $file{RSSDB}; # $tmp->{$url} = $xml; # $file{RSSDB} = $tmp; # undef $tmp; # untie %file; # # #Display RSS header # $c = 0; # foreach my $rssHeader ( $xml->get_item() ) { # $lb2_msg->add($c); # my $counter = 0; # foreach my $tag qw(title pubDate link) { # my $output = $rssHeader->$tag() || '-'; # if ($counter == 2) { $counter = 0; } # else { $counter++; } # $lb2_msg->itemCreate($c, $counter, # -itemtype => 'text', # -style => $lb2_b2, # -text => $output, # ); # $c++; # } # if ($DEBUG) { # print "\n" . '-'x79 . "\nXMLDATA:\n"; # print Dumper ($rssHeader); # print "\n"; # } # $mw->update; # } # } # else { # warn "Error processing URL: [$url]\n$!"; # return (0); # } # } # else { # warn "Error downloading URL: [$url]\n$!"; # return (0); # } # return (1); #} sub nntpconnect #----------------------------------------------------- { #Called whenever a connection to the server needs to be established my($nntp, $serv, $user, $pass,); eval { dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; $serv = $OPT{Serv}; $user = $OPT{User}; $pass = $OPT{Pass}; dbmclose %OPT }; if (! $@) { #db access ok, try to connect foreach my $c (1..3) { warn "Connection attempt: [$c of 3]\n"; undef $nntp; $nntp = Net::NNTP->new( $serv, Debug => 1, Timeout => 5, ); if (! $nntp) { if ($c > 2) { #could not connect warn "Error - Can't connect to server: [" . $serv . "]\n"; error('login'); return (0); } else { #wait 1 second and then try again (up to 3 retries) my $a = $c + 1; warn "Error - Can't connect to server: [" . $serv . "]\n"; warn "Connection attempt: [$a of 3]\n"; update_status("Connection attempt: [$a of 3]"); $mw->after(1000); next; } } else { #connected last; } } } else { #error accessing db, not connected warn "DB error: [$@]\n"; return (0); } #connection ok, authenticate user if (! defined $user) { $user = 'anonymous'; } if (! defined $pass) { $pass = "\n"; } eval { $nntp->authinfo($user, $pass) }; if ($@) { error('login'); return (0); } elsif (! $nntp) { error('login'); return (0); } #authenticated, return a reference to the nntp object return (\$nntp); } sub scan #------------------------------------------------------------ { #called from a button pressed in the main window or rc menu my (@sel,); #clear old newsgroup's display $mw->Busy(-recurse => 1); $lb2_msg->focus; $lb2_msg->delete('all'); $mw->update; #determine which newsgroup to scan @sel = $lb1_grp->selectionGet; if (defined $sel[0]) { my ($group, $nntpRef,); $group = $lb1_grp->itemCget($sel[0], 0, -text); #connect to nntp server update_status('Connecting to NNTP server...'); update_status('4', 'PROGRESSBAR'); $nntpRef = nntpconnect(); if($nntpRef) { #determine article number range available on the server my($dlmsg, $totmsgs, $rng, @nfo,); update_status('Scanning newsgroup...'); update_status('4', 'PROGRESSBAR'); @nfo = $$nntpRef->group($group); if ($nfo[1]) { shift @nfo; pop @nfo; $totmsgs = $nfo[1] - $nfo[0]; $totmsgs++; $dlmsg = $nfo[1] - $nfo[0]; $dlmsg++; #load group db and determine which headers to download next. if (-e "$group.grp") { update_status('Removing expired articles...'); update_status('4', 'PROGRESSBAR'); tie my %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); #remove expired articles my $tmp = $file{HEADERDB}; while (my $k = each %$tmp) { #k is a message number of a previously downloaded message #remove messages that are no longer on the server if ($k < $nfo[0]) { delete $tmp->{$k}; } } #determine the new article range my(@keys, $last,); update_status('4', 'PROGRESSBAR'); @keys = (keys %$tmp); @keys = sort {$b <=> $a} @keys; $last = $keys[0] || 0; warn "last messageID downloaded is: [$last]\n"; warn "first new messageID is: [$nfo[1]]\n"; $last++; #save to db $file{HEADERDB} = $tmp; undef $tmp; untie %file; #check for new messages if ($last <= $nfo[1]) { #there are new messages in the newsgroup undef $rng; unless($last == 1) { shift @nfo; unshift (@nfo, "$last"); } $totmsgs = $nfo[1] - $nfo[0]; $totmsgs++; $dlmsg = $nfo[1] - $nfo[0]; $dlmsg++; warn "There are $totmsgs new messages on the server\n"; #enforce maximum message download limit unless($totmsgs < 5000) { my $r = msglimit($totmsgs); if ($r == 0) { $$nntpRef->quit(); $dlmsg = 0; } elsif ($dlnew == 1) { $nfo[0] = $nfo[1] - $r; $dlmsg = $nfo[1] - $nfo[0]; } else { $nfo[1] = $nfo[0] + $r; $dlmsg = $nfo[1] - $nfo[0]; } } } else { #there are no new messages on the newsgroup warn "No new messages...\n"; update_status('No new messages...'); undef $dlmsg; #load old messages displayheaders('OLD'); } } else { #Newly subscribed or reset group; create a new db. warn "Creating a new db for $group\n"; update_status('Initializing newsgroup...'); update_status('4', 'PROGRESSBAR'); unless($totmsgs < 5000) { #popup d/l messages my $r = msglimit($totmsgs); warn "Message download limit set to: [$r]\n"; if ($r == 0) { $dlmsg = 0; } elsif ($dlnew == 1) { $nfo[0] = $nfo[1] - $r; } else { $nfo[1] = $nfo[0] + $r; } $dlmsg = $nfo[1] - $nfo[0]; } } if ($dlmsg) { $rng = \@nfo; #download xover into %xover, then disconnect update_status("Downloading $dlmsg of $totmsgs new headers"); update_status('16', 'PROGRESSBAR'); my($href, %xover); $href = $$nntpRef->xover($rng); #<-blocks if ($href) { %xover = %$href; undef $href; #(%xover is a HoA) $_ is msgnum #$xover{$_}[0] #subject #$xover{$_}[4] #references #$xover{$_}[1] #from #$xover{$_}[5] #bytes #$xover{$_}[2] #date #$xover{$_}[6] #lines *parts* #$xover{$_}[3] #msg-id #$xover{$_}[7] #xref:full *read* } else { #try to reconnect warn "Warning - retrying header download...\n"; update_status('retrying header download...'); eval { $$nntpRef->quit }; $nntpRef = nntpconnect(); if ($$nntpRef) { $$nntpRef->group($group); $href = $$nntpRef->xover($rng); #<-blocks if (defined $href) { %xover = %$href; undef $href; } else { error('connect'); } } else { error('connect'); } } if (keys %xover) { #handle multipart messages update_status('Preparing messages...'); my (%file, $tmp,); my $subj_sav = ' '; my $c = 0; tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' || error('MLDBM', 'DIE',); $tmp = $file{MULTIPARTDB}; while (my $k = each %xover) { if ($pb >= 100) {$pb = 0; } if ($c > 500) {for(1..1){$pb++; $c = 0; $mw->update;}} else {$c++; } if ($xover{$k}[0] =~ m/ (\p{Any}+) #the main subject $1 [\(\[\{]+? #followed by a ( or [ or { (\d+) #followed by a digit $2 [\/\-]+? #followed by a foward slash or dash (\d+) #followed by a digit $3 [\)\]\}]+? #followed by a ) or ] or } (\p{Any}*) #additional subject text $4 #$1 = sub, $2 = pt, $3 = tot, $4 = more sub /gx) { #it is a multipart message my $newsubj; $newsubj = $1.$4; $newsubj =~ s/ #validate subject ~::~/ #replace this ____ #with this /x; #record multipart message ids $tmp->{$newsubj}{$2} = $xover{$k}[3]; #combine parts for display, update subject if ($1 ne $subj_sav) { $subj_sav = $1; #it is a new subject $xover{$k}[6] = $3; #parts total $xover{$k}[0] = $newsubj; #edited subject } else { #seen this subject already delete $xover{$k}; } } else { #not a multipart message $xover{$k}[6] = 1; } } $file{MULTIPARTDB} = $tmp; undef $tmp; #remove multipart duplicates and log new message ids update_status('Combining parts...'); update_status('16', 'PROGRESSBAR'); my %seen; while (my $k = each %xover) { if ($xover{$k}) { if ($xover{$k}[6] != 1) { my $l; $l = $xover{$k}[0] . $xover{$k}[1] . $xover{$k}[6]; # subject from parts if (defined $seen{$l}) { delete $xover{$k}; } $seen{$l} = 1; } } } undef %seen; #count headers, format the time/date, remove old headers update_status('Formatting Time/Date...'); update_status('16', 'PROGRESSBAR'); while (my $k = each %xover) { my $now = time; my $epoch = str2time($xover{$k}[2]); my $age = $now - $epoch; if ($age < 2592000 or $k == $nfo [1]) { chomp($xover{$k}[2] = ctime($epoch)); } else { delete $xover{$k} } } #save new headers to group header db my $newheadercount = 0; update_status('Saving Group...'); update_status('16', 'PROGRESSBAR'); $tmp = $file{HEADERDB}; while (my $messagenumber = each %xover) { for my $c (0..7) { $tmp->{$messagenumber}[$c] = $xover{$messagenumber}[$c]; } $newheadercount++; } $file{HEADERDB} = $tmp; undef $tmp; untie %file; #display messages warn "Displaying [$newheadercount] new messages\n"; update_status('Displaying messages...'); update_status('4', 'PROGRESSBAR'); displayheaders('NEW', $newheadercount); #update last scanned time dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\n$!"; my $stime; chomp ($stime = ctime(time)); $SBSCRIBE{"$group"} = "$stime"; dbmclose %SBSCRIBE; } } } else { #unable to get group information from server } $$nntpRef->quit(); } else { #could not connect to server } } else { #no group selected to scan error('scan_1'); } #cleanup gui display_groups(); $lb2_msg->focus; if ($sel[0]) { $lb1_grp->selectionSet($sel[0]) }; ready(); return (1); } sub msglimit #-------------------------------------------------------- { #called from sub scan my $totmsgs = $_[0] || 'NULL'; $dlnew = 0; my $ret; $ret = dlmsgs('PROMPT', $totmsgs); $ret = dlmsgs($ret, $totmsgs); #return number of messages to be downloaded to the scan subroutine return ($ret); } sub dlmsgs #---------------------------------------------------------- { #called from sub msglimit my $opt = uc($_[0]) || 'NULL'; my $totmsgs = $_[1] || 'NULL'; my $maxdl = $e1_msgs->get || '5000'; $e1_msgs->delete(0, 'end'); $e1_msgs->insert(0, $maxdl); if ($opt eq 'PROMPT') { $l1_msgs->configure(-text => "There are more than $totmsgs ". 'unread messages in this group.'); $tl9->Popup; $e1_msgs->focus; $mw->update; $mw->Unbusy; $mw->waitVariable(\$msglimitOption); #wait for user $mw->Busy(-recurse => 1); return ($msglimitOption); } elsif ($opt eq 'OK') { if ($maxdl =~ m/\D/) { $maxdl = 0; } elsif ($maxdl > $totmsgs) { $maxdl = $totmsgs; } $tl9->withdraw; } elsif ($opt eq 'CANCEL') { $maxdl = 0; $tl9->withdraw; } else { warn "ERROR - Invalid dlmsgs option: [$opt]\n$!"; $mw->destroy; } return ($maxdl); } sub displayheaders #-------------------------------------------------- { #called from subs scan and message_load #when opt1 is set to 'NEW' then opt2 should specify # of new msgs my $opt1 = uc($_[0]) || 'OLD'; my $opt2 = $_[1] || 0; my(%file, $lb2_k1, $lb2_k2, $lb2_b1, $lb2_b2, $chek, $group, @sel); #determine which newsgroup is selected @sel = $lb1_grp->selectionGet; if (defined $sel[0]) { $group = $lb1_grp->itemCget($sel[0], 0, -text); } else { warn "Warning - No valid group selected for header display\n"; return (0); } #prepare display { my $imagedata = load_image(2); $chek = $mw->Photo( -format => 'bmp', -data => $imagedata ); } $lb2_b1 = $lb2_msg->ItemStyle('text', -anchor => 'e', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#0000ff', -font => '{Arial} 8', ); $lb2_b2 = $lb2_msg->ItemStyle('text', -anchor => 'w', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#0000ff', -font => '{Arial} 8', ); $lb2_k1 = $lb2_msg->ItemStyle('text', -anchor => 'e', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#000000', -font => '{Arial} 8', ); $lb2_k2 = $lb2_msg->ItemStyle('text', -anchor => 'w', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#000000', -font => '{Arial} 8', ); #load group db tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); my $tmp = $file{HEADERDB}; #display headers #TODO - my $cnt = my $c = 0; #discussion threads for (1..10) { $pb++; $mw->update; } foreach my $k (reverse sort keys %$tmp) { if ($pb >= 100) { $pb = 0; } if ($c > 100) { for(1..10) { $pb++; $mw->update; $c = 0; } } else { $c++; } $cnt++; if ($opt1 eq 'NEW' && $cnt <= $opt2) { $lb2_msg->add($k); my $counter = 0; foreach my $headerportion (0, 1, 6, 5, 2,) { if ($counter < 2) { $lb2_msg->itemCreate($k, $counter, -itemtype => 'text', -style => $lb2_b2, -text => $tmp->{$k}[$headerportion], ); } else { $lb2_msg->itemCreate($k, $counter, -itemtype => 'text', -style => $lb2_b1, -text => $tmp->{$k}[$headerportion], ); } $counter++; } } else { $lb2_msg->add($k); my $counter = 0; foreach my $headerportion (0, 1, 6, 5, 2,) { if ($counter < 2) { $lb2_msg->itemCreate($k, $counter, -itemtype => 'text', -style => $lb2_k2, -text => $tmp->{$k}[$headerportion], ); } else { $lb2_msg->itemCreate($k, $counter, -itemtype => 'text', -style => $lb2_k1, -text => $tmp->{$k}[$headerportion], ); } $counter++; } #mark message as old $tmp->{$k}[8] = 1; } if ($tmp->{$k}[7] eq 'read') { $lb2_msg->indicator('create', $k, -itemtype => 'image', -image => $chek ); } ##mark message as old #$tmp->{$k}[8] = 1; } $file{HEADERDB} = $tmp; undef $tmp; untie %file; return (1); } sub message_load #---------------------------------------------------- { #called from the main window, rc menu, or sub message_clear my($group, $return,); update_status('Loading newsgroup...'); $mw->Busy(-recurse => 1); $lb2_msg->focus; $mw->update; #load messages $lb2_msg->delete('all'); for (1..10) { $pb++; $mw->update; } my $ret = displayheaders('OLD'); if ($ret) { $return = 1; } else { $return = 0; } ready(); $lb2_msg->focus; $mw->update; return ($return); } sub message_clear #--------------------------------------------------- { #called from the rc menu my ($group, @sel, @paths, %file,); update_status('Clearing previously scanned messages from group...'); $mw->Busy(-recurse => 1); $mw->update; #get a list of paths for the message hlist lb2_msg_select_all(); @paths = $lb2_msg->infoSelection; unless ($paths[0]) { my $ret = displayheaders('OLD'); if ($ret) { lb2_msg_select_all(); @paths = $lb2_msg->infoSelection; } else { warn "Error - Unable to clear group\n"; ready(); $mw->Unbusy; return (0); } } #determine group @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected to clear.\n"; ready(); return (0); } #open the selected groups' DB file if (-e "$group.grp") { tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); #select this groups HEADERDB table my $tmp = $file{HEADERDB}; #clear the selected group, and update it's HEADERDB my $c = 1; foreach my $path (@paths) { unless ($path == $paths[0]) { $lb2_msg->delete('entry', $path); delete $tmp->{$path}; if ($c == 100) { $mw->update; undef $c; } $c++; } } $file{HEADERDB} = $tmp; #delete this groups' MULTIPARTDB delete $file{MULTIPARTDB}; } #close the DB file and finish up untie %file; ready(); return (1); } sub lb2_msg_sort #---------------------------------------------------- { #called from a button pressed in the main window my $caller = uc($_[0]) || ' '; my(@sel, $group, @y, $imagedata, $chek, %file, $c, $col, $opt,); if ($caller eq ' ') { return (1); } elsif ($caller eq 'HEADERS') { $col = 0; $opt = 1; } elsif ($caller eq 'FROM') { $col = 1; $opt = 1; } elsif ($caller eq 'PARTS') { $col = 6; $opt = 2; } elsif ($caller eq 'BYTES') { $col = 5; $opt = 2; } elsif ($caller eq 'DATE') { $col = 2; $opt = 3; } else { warn "Error - Invalid sort option: [$caller]\n"; return (0); } $sort_cnt++; $mw->Busy(-recurse => 1,); $lb2_msg->delete('all'); update_status ('Sorting...'); $imagedata = load_image(2); $chek = $mw->Photo( -format => 'bmp', -data => $imagedata ); undef $imagedata; #load group header db @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for sort.\n"; $mw->Unbusy; return (0); } tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); my $tmp = $file{HEADERDB}; #sort if ($sort_cnt % 2) { if ($opt == 1) { @y = sort{ $tmp->{$b}[$col] cmp $tmp->{$a}[$col] }keys %$tmp; } elsif ($opt == 2) { @y = sort{ $tmp->{$b}[$col] <=> $tmp->{$a}[$col] }keys %$tmp; } else { @y = sort{ str2time($tmp->{$b}[$col]) <=> str2time($tmp->{$a}[$col]) }keys %$tmp; } } else { if ($opt == 1) { @y = sort{ $tmp->{$a}[$col] cmp $tmp->{$b}[$col] }keys %$tmp; } elsif ($opt == 2) { @y = sort{ $tmp->{$a}[$col] <=> $tmp->{$b}[$col] }keys %$tmp; } else { @y = sort{ str2time($tmp->{$a}[$col]) <=> str2time($tmp->{$b}[$col]) }keys %$tmp; } } #re-populate my $blackEast = $lb2_msg->ItemStyle( 'text', -selectforeground => '#fff000', -bg => '#ffffff', -fg => 'black', -anchor => 'e', -font => '{Arial} 8', ); my $blackWest = $lb2_msg->ItemStyle( 'text', -selectforeground => '#fff000', -bg => '#ffffff', -fg => 'black', -anchor => 'w', -font => '{Arial} 8', ); my $blueEast = $lb2_msg->ItemStyle( 'text', -selectforeground => '#fff000', -bg => '#ffffff', -fg => 'blue', -anchor => 'e', -font => '{Arial} 8', ); my $blueWest = $lb2_msg->ItemStyle( 'text', -selectforeground => '#fff000', -bg => '#ffffff', -fg => 'blue', -anchor => 'w', -font => '{Arial} 8', ); $c = 0; foreach my $k (@y) { if ($c > 100) { $mw->update; $c = 0; } else { $c++ } my($lb2_k1, $lb2_k2,); if ($tmp->{$k}[8]) { $lb2_k1 = $blackEast; $lb2_k2 = $blackWest; } else { $lb2_k1 = $blueEast; $lb2_k2 = $blueWest; } $lb2_msg->add($k); $lb2_msg->itemCreate($k, 0, -itemtype => 'text', -style => $lb2_k2, -text => $tmp->{$k}[0] ); $lb2_msg->itemCreate($k, 1, -itemtype => 'text', -style => $lb2_k2, -text => $tmp->{$k}[1] ); $lb2_msg->itemCreate($k, 2, -itemtype => 'text', -style => $lb2_k1, -text => $tmp->{$k}[6] ); $lb2_msg->itemCreate($k, 3, -itemtype => 'text', -style => $lb2_k1, -text => $tmp->{$k}[5] ); $lb2_msg->itemCreate($k, 4, -itemtype => 'text', -style => $lb2_k1, -text => $tmp->{$k}[2] ); if ($tmp->{$k}[7] eq 'read') { $lb2_msg->indicator('create', $k, -itemtype => 'image', -image => $chek ); } } untie %file; ready(); return (1); } sub grab #------------------------------------------------------------ { #called from a button in the main window or the rc menu my $open = $_[0] || '0'; my (%file, @sel, @grabs, $group,); $mw->Busy(-recurse => 1); $pb = 0; #grab what? @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for grab.\n"; ready(); return (0); } $lb2_msg->focus; @grabs = $lb2_msg->selectionGet; unless ($group and @grabs) { warn "Selection error\n"; $mw->Unbusy; ready(); return (0); } #load group multipart db (%multi is a HoHoA) subj->part = msg id tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); #get selection(s) foreach my $article (@grabs) { my($subje, $parts, @art, $aref, $treturn,); for(1..4) { $pb++; $mw->update; } $mw->after(500); #read subject $subje = $lb2_msg->itemCget($article, 0, -text); $parts = $lb2_msg->itemCget($article, 2, -text); #lookup subject if (defined $parts && $parts > 1) { #Multipart message my $tmp = $file{MULTIPARTDB}{$subje}; my $parttot = keys %$tmp; my @parts = sort(keys %$tmp); my $msgIDs; my $partNum; #fixes: part 0 nfo files in messages if ($parttot == $parts + 1) { $partNum = 0; } else { $partNum ++; } #prepare options to provide to the thread my $msgID = ' '; foreach my $msgID (@parts) { $msgIDs .= $tmp->{$msgID} . ','; } $msgID = substr ($msgID, 1); #launch thread, download parts $shash{1}{optionCSV} = '0,' . $partNum . ',' . $msgIDs; $shash{1}{progress} = 1; $shash{1}{article} = 1; $mw->after(500); while ($shash{1}{article} == 1) { #wait for the thread, update gui update_status( "Downloading... ($shash{1}{progress} of $parttot)" ); if ($pb >= 100) { $pb = 0; } else { $pb += .01; } $mw->update; } $treturn = $shash{1}{return}; } else { #singlepart message my @a = ($article); for(1..5) { $pb += 5; $mw->update; } update_status('Downloading message...'); #Launch thread $shash{1}{optionCSV} = $group . ",1,@a"; $shash{1}{article} = 1; while ($shash{1}{article} == 1) { if ($pb >= 100) { $pb = 0; } else { $pb += .01; } $mw->update; } $treturn = $shash{1}{return}; } #If the message was downloaded ok, then start up the decoder if ($treturn) { my ($res, $cvt, $ret,); $mw->after(500); update_status('Decoding attachment...'); warn "Decoding attachment(s)\n"; $pb++; #launch thread $shash{1}{decode} = 1; while ($shash{1}{decode} == 1) { if ($pb >= 100) { $pb = 0; } else { $pb += .01; } $mw->update; } $ret = $shash{1}{return}; if ($ret) { #mark read; load and update group header db my($tmp, $chek,); $tmp = $file{HEADERDB}; $tmp->{$article}[7] = 'read'; $file{HEADERDB} = $tmp; { my $imagedata = load_image(2); $chek = $mw->Photo( -format => 'bmp', -data => $imagedata ); } $lb2_msg->indicator( 'create', $article, -itemtype => 'image', -image => $chek ); $mw->update; #open it? if ($open == 1) { my $cwd = cwd; my($dir, $file) = split('\*', $ret); if ($^O eq 'MSWin32') { chdir ($dir); system('start', '/B', $file); chdir ($cwd); } else { chdir ($dir); system($file); chdir ($cwd); } } } else { #Could not decode attachment, try the next message warn "Could not decode attachment\n$!"; } } else { #Missing some or all parts, try the next message warn "Will not be able to decode this attachment\n"; warn "The message is missing some or all of it's parts\n$!"; } } untie %file; ready(); return (1); } sub message_delete #-------------------------------------------------- { #called from the rc menu my($group, @sel, %file,); $mw->Busy(-recurse => 1,); update_status('Deleting selected messages...'); @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for message delete.\n"; ready(); $mw->Unbusy; return (0); } $lb2_msg->focus; @sel = $lb2_msg->selectionGet; unless ($sel[0]) { ready(); $mw->Unbusy; return (0); } #load group header db if (-e "$group.grp") { tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); } else { ready(); $mw->Unbusy; return (0); } my $tmp = $file{HEADERDB}; #delete selected messages from screen and group header db foreach my $msgnum (@sel) { $lb2_msg->hide('entry', $msgnum); delete $tmp->{$msgnum}; } $file{HEADERDB} = $tmp; $lb2_msg->selectionClear; $mw->update; untie %file; ready(); return (1); } sub read_message #---------------------------------------------------- { #called from a button pressed in the main window or the rc menu my (@sel, $group,); $mw->Busy(-recurse => 1,); update_status('Connecting to server...'); #determine which group @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for message read.\n"; ready(); return (0); } #determine which article @sel = $lb2_msg->selectionGet; $lb2_msg->focus; if (! $group || ! $sel[0]) { ready(); return (0); } else { #Connect to server my ($nntpRef, $msg,); $nntpRef = nntpconnect(); if (! $nntpRef) { warn "Retrying connection...\n"; $nntpRef = nntpconnect(); if (! $nntpRef) { warn "Error - Unable to connect to server, try again\n"; ready(); return (0); } } #download message update_status('Downloading message...'); #launch thread, download article #<-test $shash{1}{optionCSV} = $group . ',1,' . $sel[0]; $shash{1}{article} = 1; update_status('Downloading message'); while ($shash{1}{article} == 1) { #wait for the thread, update gui if ($pb >= 100) { $pb = 0; } else { $pb += .01; } $mw->update; } #check return, read and display downloaded message file my $treturn = $shash{1}{return}; if ($treturn) { #read message header and determine encoding (max 128 lines) my ($headerLineCount, $blankLineAt, $encoding, $qp,); open (FH, '<', 'part1.pt') || warn "Can't open part1.pt\n$!"; while (my $l = ()) { #determine if we are still in the header $headerLineCount++; if ($l =~ m/^$/) { #this line is blank $blankLineAt = $headerLineCount; if ($blankLineAt = $headerLineCount - 1) { #the last line was blank also (indicates end of header) last; } } #determine encoding used for this message if ($l =~ m/Content-Transfer-Encoding:\s+(.+)/) { #record specified encoding $encoding = $1; if ($encoding eq 'quoted-printable') { $qp = 1; } last; } elsif ($headerLineCount > 128) { #extra long header? (sanity check) last; } } if (!defined $encoding) { $encoding = 'UNKNOWN'; } warn "DEBUG - $encoding encoding detected\n"; close FH || warn "Can't close part1.pt"; #read message open (FH, '<', 'part1.pt') || warn "Can't open part1.pt\n$!"; while (my $l = ()) { #check if quoted printable encoding was detected if ($qp) { #decode quoted printable #The following is borrowed from the module #MIME::QuotedPrint::Perl created by: Gisle Aas $l =~ s/\r\n/\n/g; # normalize newlines $l =~ s/[ \t]+\n/\n/g; # rule #3 (trailing space deleted) $l =~ s/=\n//g; # rule #5 (soft line breaks) if (ord('A') == 193) { # EBCDIC style machine if (ord('[') == 173) { $l =~ s/=([\da-fA-F]{2})/Encode::encode('cp1047', Encode::decode('iso-8859-1',pack("C", hex($1))))/gex; } elsif (ord('[') == 187) { $l =~ s/=([\da-fA-F]{2})/Encode::encode('posix-bc', Encode::decode('iso-8859-1',pack("C", hex($1))))/gex; } elsif (ord('[') == 186) { $l =~ s/=([\da-fA-F]{2})/Encode::encode('cp37', Encode::decode('iso-8859-1',pack("C", hex($1))))/gex; } } else { # ASCII style machine $l =~ s/=([\da-fA-F]{2})/pack("C", hex($1))/ge; } } elsif ($l =~ s/[A-F]\<(.*)\>/$1/) { #handle (remove for now) odd 'tin-like' formatting tags <-todo } #display message $txt_read->insert('end', $l); $mw->update; } close FH || warn "Can't close part1.pt"; } else { #error downloading message update_status('Could not download article from server'); warn "Could not download the article\n"; $mw->update; $mw->after(1000); ready(); return (0); } } #mark message as read, load and update group header db, update gui { my ($tmp, %file, $imagedata, $chek,); tie %file, 'MLDBM', "$group.grp", O_CREAT|O_RDWR, '0640' or error('MLDBM', 'DIE',); $tmp = $file{HEADERDB}; $tmp->{$sel[0]}[7] = 'read'; $file{HEADERDB} = $tmp; untie %file; $txt_read->focus; $tl4->update; $tl4->deiconify(); $tl4->raise(); $mw->update; $imagedata = load_image(2); $chek = $mw->Photo( -format => 'bmp', -data => $imagedata ); undef $imagedata; $lb2_msg->indicator('create', $sel[0], -itemtype => 'image', -image => $chek ); } ready(); return (1); } sub read_close #------------------------------------------------------ { #called from a button pressed in the read window $txt_read->delete("1.0", 'end'); $tl4->withdraw; $tl4->configure(-title => 'Read Message'); $mw->update; return (1); } sub read_reply #------------------------------------------------------ { #called from a button pressed in the read window my($c, $txt, @tmp, $refs, $mid, $subj); $mw->Busy(-recurse => 1); #read message $txt = $txt_read->get('1.0', 'end'); @tmp = split('\n', $txt); undef $txt; $txt_read->delete("1.0", 'end'); $tl4->withdraw; dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; my $QSig = $OPT{QSig}; dbmclose %OPT; #process header, and quote message $c = 0; foreach my $line (@tmp) { #look for blank lines (delimits header) unless ($line =~ m/(.+)/) { $c++; } if ($c >= 1) { #found blank lines, start quoting everything from here if ($line =~ m/^--\s*$/) { #found signature if ($QSig) { $txt .= '> '.$line."\n"; } else { last; } } else { #quote message line $txt .= '> '.$line."\n"; } } else { #process header if ($line =~ m/^References:\s+(.+)/) { $refs = $1; #warn "gotref, refs is $refs\n"; } elsif ($line =~ m/^Message-ID:\s+(.+)/) { $mid = $1; #warn "gotmid, mid is $mid\n"; } elsif ($line =~ m/^Subject:\s+(.+)/) { my $a = $1; if ($a =~ m/[Rr][Ee]:.*/) { $subj = $a; } else { $subj = "Re: $a"; } #warn "gotsubj, subj is $subj\n"; } } } if ($mid) { $refs .= $mid }; #save references, so the send function can craft the header dbmopen(%OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; $OPT{Refs} = $refs; $post_entries[0]->delete('0', 'end'); $post_entries[1]->delete('0', 'end'); $post_entries[0]->insert('end', $OPT{Mail}); $post_entries[1]->insert('end', $subj); dbmclose %OPT; #populate then display the reply window my $typed; my @sel = $lb2_msg->selectionGet; $typed = $lb2_msg->itemCget($sel[0], 1, -text); $typed .= " wrote in message-id: $mid\n"; $txt_post->insert('end', "$typed\n"); $txt_post->insert('end', "$txt"); $mw->Unbusy; post(); return (1); } sub post #------------------------------------------------------------ { #called from a main button, rc menu, or read_reply my($from,); my @sel = $lb1_grp->selectionGet; my $group; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for message post.\n"; return (0); } { dbmopen(my %OPT, 'settings', '0640') || die "Cannot read settings.\n$!"; $from = $OPT{Mail}; dbmclose %OPT } $post_entries[0]->delete('0', 'end'); $post_entries[0]->insert('end', $from); $tl2->deiconify(); $tl2->raise(); $txt_post->focus; $mw->update; return (1); } sub post_post #------------------------------------------------------- { #called from a button pressed in the post window or sub post_attach my $atch = $_[0] || 0; my $cur = $_[1] || 1; my $tot = $_[2] || 1; my $filename = $_[3] || ' '; my (@sel, $msg, $hdr, $bdy, $subj, $from, $refs, $sign, $group,); #determine group @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected for posting to.\n"; return (0); } $mw->update; $mw->Busy(-recurse => 1,); update_status('Formatting message...'); for (1..4) { $pb++; $mw->update; } #gather message information, update subject header #subject should not contain (#/#), it should look like below #[Comment1] "filename" yEnc (partnum/numparts) [size] [Comment2] $subj = $post_entries[1]->get(); $from = $post_entries[0]->get(); $subj =~ s/\(\d+\/\d+\)//g; { dbmopen(my %OPT, 'settings', '0640') || die "Cannot read settings.\n$!"; unless ($sign) { $sign = 0; } unless ($subj) { $subj = 'No Subject'; } unless ($OPT{Refs}) { $OPT{Refs} = 0; } if ($atch) { $subj .= ' "'.$filename.'" '. "yEnc ($cur/$tot)"; } $refs = $OPT{Refs}; $sign = $OPT{Sig}; $OPT{Refs} = 0; #<-test dbmclose %OPT; } #create header if ($refs) { $hdr = 'From: '."$from\n". 'Newsgroups: '."$group\n". 'Distribution: '."world\n". 'References: '."$refs\n". 'X-NNTPclient: '."NewsSurfer v3.01\n". 'X-CreatedBy: '." Just another Perl hacker, \n". 'Subject: '."$subj\n\n"; } else { $hdr = 'From: '."$from\n". 'Newsgroups: '."$group\n". 'Distribution: '."world\n". 'X-NNTPclient: '."NewsSurfer v3.01\n". 'X-CreatedBy: '." Just another Perl hacker, \n". 'Subject: '."$subj\n\n"; } $msg = $hdr."\n"; #attach body and signature to first article only if ($cur == 1) { $bdy = $txt_post->get("1.0", 'end'); if ($sign) { #attach signature to body my (@sig,); if (open ('FH', '<', $sign)) { @sig = (); close FH; $bdy .= "\n--\n"; #'four lines after the double dash space' foreach my $line (@sig) { chomp $line; $bdy .= "$line\n"; } } else { error('sig1') } } $msg .= $bdy."\n"; } if ($atch) { #attach file part to article $msg .= $atch."\n"; } #connect, post message, and disconnect update_status('Posting message...'); for (1..4) { $pb += 4; $mw->update; } my $nntpRef = nntpconnect(); if($nntpRef) { my $bool = $$nntpRef->post([$msg]); #<-blocks unless ($bool) { warn "Error - Unable to post message, could not post.\n"; } } else { warn "Error - Unable to post message, could not connect\n"; } $$nntpRef->quit; #finish up ready(); if ($cur == $tot) { post_cancel(); } return (1); } sub post_yenc #------------------------------------------------------- { my $aref = $_[0] || return (0); my (@in, @out, $linesize,); update_status('Encoding attachment...'); for (1..4) { $pb++; $mw->update; } @in = @$aref; undef $aref; $linesize = 0; while (defined(my $byte = shift @in)) { my $yenc = ($byte + 42) % 256; if ($linesize >= 128) { #enforce line size, insert a CRLF pair push @out, 0x0D; push @out, 0x0A; $linesize = 0; } if ($linesize == 0 || $linesize == 127) { #escape a tab or space in the first or last column of a line if ($yenc == 0x09 || $yenc == 0x20) { $yenc = ( $byte + 64 ) % 256; push @out, 0x3D; push @out, $yenc; $linesize += 2; next; } } if ($yenc==0x00 || $yenc==0x0A || $yenc==0x0D || $yenc==0x3D ){ #found a critical character, escape it with 0x3D (=) push @out, 0x3D; $yenc = ( $byte + 64 ) % 256; $linesize++; } push @out, $yenc; $linesize++; } my $ydata = join '', map { chr $_ } @out; return ($ydata); } sub post_attachandpost #---------------------------------------------- { #called from a button pressed in the post window my $file = $mw->getOpenFile(); $mw->Busy(-recurse => 1); if (defined $file) { #Open the file, or return early unless (open ('ATCH', '< :raw', $file)) { error('post_atch_1'); return (0); } #deterimine filename and remove leading and trailing whitespace update_status('Creating attachment...'); for (1..4) { $pb += 4; $mw->update; } my $file_name = $file; $file_name =~ s/ .*\/ #0 or more of anything until foward slash (.+) #capture 1 or more of anything /$1 #replace those with capture variable 1 /x; $file_name =~ s/ ^\s+ #remove any space at the beginning //x; $file_name =~ s/ \s+$ #remove any space at the end //x; #truncate filenames longer than 254 characters my $file_name_len = length $file_name; if ($file_name_len >= 255) { for (255..$file_name_len) { chop $file_name; } } #how large is the file in bytes my($file_bytes, $file_kbytes,); $file_bytes = -s $file; #Split files that are larger than the posting limit (400k) <-research if ($file_bytes > 409600) { #how many parts will this be? my($totalparts, $currentpart,); $totalparts = 1 + (int($file_bytes / 409600)); #read 400k chunks of the file into a string $currentpart = 1; while (my $size = read(ATCH, my $buf, 409600)) { #yEncode data my @data = map { ord $_ } split(//, $buf); my $ydata = post_yenc(\@data); my $begin = 1 + (($currentpart * 409600) - 409600); my $end; if ($size == 409600) { $end = $currentpart * $size; } else { $end = (($currentpart - 1) * 409600) + $size; } #Encapsulate data in yENC headers my $crlf = "\015\012"; my $yhead = "=ybegin ". "part=$currentpart ". "total=$totalparts ". "line=128 ". "size=$file_bytes ". "name=$file_name"; my $ypart = "=ypart ". "begin=$begin ". "end=$end"; my $ytail = "=yend ". "size=$size ". "part=$currentpart"; my $atch = $crlf.$yhead.$crlf.$ypart. $crlf.$ydata.$crlf.$ytail.$crlf; #Send the message my $ret = post_post( $atch, $currentpart, $totalparts, $file_name ); if ($ret) { $currentpart++; } else { warn "Error - Unable to attach file\n"; last; } $mw->update; } close ATCH; } else { #Create single part message attachement my $line; while () { $line .= $_; } close ATCH; #yEncode data my @data = map { ord $_ } split(//, $line); my $ydata = post_yenc(\@data); #Encapsulate data in yENC headers my $crlf = "\015\012"; my $yhead = "=ybegin ". "line=128 ". "size=$file_bytes ". "name=$file_name"; my $ytail = "=yend ". "size=$file_bytes"; my $atch = $crlf.$yhead.$crlf.$ydata.$crlf.$ytail.$crlf; #send the message my $ret = post_post($atch, 1, 1, $file_name); unless ($ret) { warn "Error - Unable to attach file\n"; } } } else { warn "Warning - No file selected for attachment\n"; } $mw->Unbusy; return (1); } sub post_cancel #----------------------------------------------------- { #called from a button pressed in the post window $txt_post->delete('1.0', 'end'); $tl2->Unbusy; $tl2->withdraw(); $mw->update; return (1); } sub browse #---------------------------------------------------------- { #called from a button pressed in the main window <-test $mw->Busy(-recurse => 1); dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; if ($^O eq 'MSWin32') { my ($dir,); $dir = $OPT{DDir}; $dir =~ s/ \/ #swap foward slash for a backslash /\\/xg; system('explorer.exe', $dir); } else { system('ls', $OPT{DDir}); } dbmclose %OPT; $mw->Unbusy; $mw->update; return (1); } sub rset_cmd #-------------------------------------------------------- { #called from the rc menu my ($sel, $rem,); $mw->Busy(-recurse => 1); $sel = $lb1_grp->selectionGet; $rem = $lb1_grp->itemCget($sel, 0, -text); unless ($sel && $rem) { warn "Warning - No valid groups selected for reset.\n"; return (0); } $lb2_msg->delete('all'); if (-e "$rem.grp") { unless (unlink "$rem.grp") { error('rset_1'); return (0); } } dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\a\n$!"; $SBSCRIBE{$rem} = 'Never'; dbmclose %SBSCRIBE; $mw->after(500); display_groups(); $mw->Unbusy; return (1); } sub group #----------------------------------------------------------- { #called from a button pressed in the main window my($lb_grp_s1, $imagedata, $chek,); $mw->Busy(-recurse => 1); $tl3->Popup; $lb_grp->focus; foreach my $b (@buttons) { $b->configure(-state => 'disabled'); } $mw->Unbusy; $mw->update; return (1); } sub grp_search #------------------------------------------------------ { search($lb_grp, 'GROUP'); return (1); } sub grp_shownewsgroups #---------------------------------------------- { my $opt1 = uc ($_[0]) || 'ALL'; my($chek, $lb_grp_s1,); $mw->Busy(-recurse => 1,); update_status('Loading groups...'); { my $imagedata = load_image(3); $chek = $mw->Photo(-format => 'bmp', -data => $imagedata); for (1..4) { $pb++; $mw->update; } undef $imagedata; } $lb_grp_s1 = $lb_grp->ItemStyle( 'text', -selectforeground => '#000000', -selectbackground => '#fff000', -bg => '#000000', -fg => '#ffffff', -font => '{Arial} 8', -anchor => 'w', ); $lb_grp->delete('all'); $mw->update; if (-e 'groups') { my(%groups, $counter, $c,); tie %groups, 'MLDBM', 'groups', O_CREAT|O_RDWR, '0640' or die $!; dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\n$!"; $counter = $c = 0; foreach my $k (sort(keys(%groups))) { my $found; if ($opt1 eq 'SEARCH') { #search action specified if ($k =~ m/$grpSubSearch/) { $found++; } } if ($opt1 eq 'SEARCH' && !defined $found) { #item does not match search next; } else { #display the item my $v = $groups{$k}; $lb_grp->add($counter); $lb_grp->itemCreate($counter, 0, -text => $k, -style => $lb_grp_s1 ); $lb_grp->itemCreate($counter, 1, -text => $v, -style => $lb_grp_s1 ); if ($SBSCRIBE{$k}) { $lb_grp->indicator('create', $counter, -itemtype => 'image', -image => $chek ); } } if ($pb >= 100) { $pb = 0; $mw->update; } if ($c > 1000) { for (1..5) { $pb++; $mw->update; } $c = 0; } else { $c++; } $counter++; } untie %groups; dbmclose %SBSCRIBE; } else { $lb_grp->add(0); $lb_grp->itemCreate(0,0, -text => 'Press the Update button to retrieve groups '. 'from server.' ); } ready(); return (1); } sub grp_update #------------------------------------------------------ { #called from a button pressed in the group window $tl3->Busy(-recurse => 1,); update_status('Downloading groups...'); $lb_grp->delete('all'); $pb++; $mw->update; $shash{1}{list} = 1; while ($shash{1}{list} == 1) { if ($pb >= 100) { $pb = 0; } else { $pb++; } $mw->after(100); $mw->update; } $tl3->Unbusy; grp_close(); group(); grp_shownewsgroups(); ready(); return (1); } sub grp_subscribe #--------------------------------------------------- { #called from a button pressed in the group window my ($imagedata, $chek, @sel,); $imagedata = load_image(3); $chek = $mw->Photo( -format => 'bmp', -data => $imagedata ); undef $imagedata; dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\a\n$!"; @sel = $lb_grp->selectionGet; foreach (@sel) { my $a = $lb_grp->itemCget($_, 0, -text); $lb_grp->indicator('create', $_, -itemtype => 'image', -image => $chek ); $SBSCRIBE{$a} = 'Never'; } dbmclose %SBSCRIBE; display_groups(); return (1); } sub grp_unsubscribe #------------------------------------------------- { #called from a button pressed in the group or main window my $opt = $_[0]; dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\a\n$!"; if ($opt) { #unsubscribe from main screen my($sel, $a,); $sel = $lb1_grp->selectionGet; unless ($sel) { warn "Warning - No valid group selected to remove.\n"; return (0); } $a = $lb1_grp->itemCget($sel, 0, -text); warn "Warning - Removing newsgroup: [$a.grp].\n"; delete $SBSCRIBE{$a}; if (-e "$a.grp") { unlink "$a.grp" || error('grp_unsub_1', "$a"); } $lb2_msg->delete('all'); } else { #unsubscribe from groups screen my (@sel,); @sel = $lb_grp->selectionGet; unless (@sel) { warn "Warning - No valid group selected to remove.\n"; return (0); } foreach (@sel) { my $a = $lb_grp->itemCget($_, 0, -text); $lb_grp->indicator('delete', $_,); warn "Warning - Removing newsgroup: [$a.grp].\n"; delete $SBSCRIBE{$a}; if (-e "$a.grp") { unlink "$a.grp" || error('grp_unsub_1', "$a"); } } } dbmclose %SBSCRIBE; display_groups(); return (1); } sub grp_close #------------------------------------------------------- { #called from a button pressed in the group window $mw->Busy(-recurse => 1); $mw->update; $lb_grp->delete('all'); $tl3->withdraw; foreach my $b (@buttons) { $b->configure(-state => 'normal',); } $mw->update; $mw->Unbusy; return (1); } sub display_groups #-------------------------------------------------- { #called from subs: group_subscribe, group_unsubscribe my ($lb1_k1, $lb1_k2,); $lb1_grp->delete('all'); $lb1_k1 = $lb1_grp->ItemStyle('text', -anchor => 'w', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#000000', -font => '{Arial} 8', ); $lb1_k2 = $lb1_grp->ItemStyle('text', -anchor => 'e', -selectforeground => '#fff000', -background => '#ffffff', -foreground => '#000000', -font => '{Arial} 8', ); my $counter = 1; dbmopen(my %SBSCRIBE, 'sbscribe', '0640') || die "Cannot create sbscribe.\n$!"; for my $k (sort keys %SBSCRIBE) { $lb1_grp->add($counter); $lb1_grp->itemCreate($counter, 0, -text => "$k", -style => $lb1_k1, ); $lb1_grp->itemCreate($counter, 1, -text => "$SBSCRIBE{$k}", -style => $lb1_k2, ); $counter++; } dbmclose %SBSCRIBE; return (1); } sub search_popup #---------------------------------------------------- { #called from rc menu my ($group, @sel,); #make sure the group clicked has been loaded or scanned. @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { print STDERR 'No valid group selected to search.'. "\nEval: $@\n"; } #raise popup $tla->Popup; $tla->focus; $mw->update; return (1); } sub search #---------------------------------------------------------- { #works for HLists only #called from button pressed in group window or search_popup window #Todo - add search within results option #<--Todo my $w = $_[0] || return (0); my $opt1 = uc ($_[1]) || 0; my $opt2 = $_[2] || 0; my (@paths, $c,); #show all requested if ($opt1 eq 'GROUP' && $opt2 == 1) { #show all available newsgroups grp_shownewsgroups('ALL'); return (1); } #start searching $w->focus; $mw->Busy(-recurse => 1,); update_status('Searching groups...'); $mw->update; if (defined $grpSubSearch) { #escape any regex metachars found within search string #$grpSubSearch = qr/$grpSubSearch/; $grpSubSearch =~ s/ ([\/\+\*\.\?\^\$]+) #quietly escape any regex metachars /\\$1/xg; } else { #Default search regex (all but newline regex metachar) $grpSubSearch = '.'; } for (1..4) { $pb++; $mw->update; } $tla->withdraw; $c = 0; @paths = $w->infoChildren; if ($paths[0]) { #Widget is populated, show only entries which match search term foreach my $path (@paths) { my $item = $w->itemCget($path, 0, -text); if ($item =~ m/$grpSubSearch/i) { $w->show('entry', $path); } else { $w->hide('entry', $path); } if ($c >= 1000) { if ($pb >= 100) { $pb = 0; } else { $pb++; } $c = 0; $mw->update; next; } $c++; } } else { #searching an empty widget, see if it is the groups window if ($opt1 eq 'GROUP') { #call a more specific search subroutine grp_shownewsgroups('SEARCH'); } } ready(); return (1); } sub opt #------------------------------------------------------------- { #called from button pressed in the main window dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; my $c = 0; foreach my $k qw(Serv User Pass Mail DDir Sig) { $OPT_widgets[$c]->delete(0, 'end'); $OPT_widgets[$c]->insert('end', $OPT{$k}); $mw->update; $c++ } if ($OPT{QSig}) { $OPT_widgets[$c]->select; } dbmclose %OPT; $tl5->Popup; $tl5->focus; $mw->update; return (1); } sub opt_close #------------------------------------------------------- { #called from button pressed in the option window my($serv, $user, $pass, $email, $ddir, $sig,); dbmopen(my %OPT, 'settings', '0640') || die "Cannot create settings.\n$!"; $serv = $OPT_widgets[0]->get(); $user = $OPT_widgets[1]->get(); $pass = $OPT_widgets[2]->get(); $email = $OPT_widgets[3]->get(); $ddir = $OPT_widgets[4]->get(); $sig = $OPT_widgets[5]->get(); $OPT{Serv} = $serv; $OPT{User} = $user; $OPT{Pass} = $pass; $OPT{Mail} = $email; $OPT{DDir} = $ddir; $OPT{Sig} = $sig; $OPT{QSig} = $quoteSig; dbmclose %OPT; $tl5->withdraw; $mw->update; return (1); } sub opt_download #---------------------------------------------------- { #called from button pressed in the option window my ($dir,); $OPT_widgets[4]->delete(0, 'end'); eval { $dir = $tl5->chooseDirectory( -title => 'Choose a download directory.', -initialdir => '.', -mustexist => 1, ) }; if ($@) { #error error('conf_browse1'); } else { if ($dir) { #user defined directory $OPT_widgets[4]->insert('end', "$dir"); } else { #default directory $OPT_widgets[4]->insert('end', "."); } } $mw->update; return (1); } sub opt_signature #--------------------------------------------------- { #called from button pressed in the option window my ($sig, $ofile,); $OPT_widgets[5]->delete(0, 'end'); $ofile = $tl5->getOpenFile( -title => 'Choose Signature File', -initialdir => '.', ); if ($ofile) { $OPT_widgets[5]->insert('end', "$ofile"); $mw->update; return (1); } return (0); } sub show_log #-------------------------------------------------------- { #called from button pressed in the main window my (@log,); $tl1->deiconify(); $tl1->raise(); $txt_log->focus; $txt_log->delete('1.0', 'end'); $mw->update; $mw->Busy(-recurse => 1); close STDERR; open ('FH', '<', 'NewsSurfer.log') || warn "Error - Cannot open NewsSurfer.log\n$!"; @log = (); close FH; open STDERR, '>>', 'NewsSurfer.log' || warn "Error - Cannot open NewsSurfer.log\n$!"; foreach my $line (@log) { chomp $line; unless (defined $line) { $line = '#'; } $line =~ s/ ^Net #the word 'Net' at the beginning of the string .*\) #followed by everything up to a right parenthesis (<|>.*) #capture left or right bracket followed by anything #replace those with capture 1 /$1/x; if ($line =~ m/ ^>>>.* #three right brackets followed by anything /x) { $txt_log->insert('end', $line . "\n", 'Blue'); } elsif ($line =~ m/ ^Error #the word 'error' at the beginning of the string \s* #followed by some optional whitespace -.+ #followed by a dash and 1 or more of anything /xi) { $txt_log->insert('end', $line . "\n", 'Red'); } elsif ($line =~ m/ ^Warning #the word 'warning' at the beginning of the string \s* #followed by some optional whitespace -.+ #followed by a dash and 1 or more of anything /xi) { $txt_log->insert('end', $line . "\n", 'Yellow'); } else { $txt_log->insert('end', $line . "\n"); } $mw->update; } $mw->Unbusy; return (1); } sub log_close #------------------------------------------------------- { #called from button pressed in the log window $tl1->withdraw; $mw->update; return (1); } sub log_save #-------------------------------------------------------- { #called from button pressed in the log window my (@log, $sf,); $sf = $tl1->getSaveFile(-title => 'Save Log',); if ($sf) { warn "Warning - A logfile has been saved.\n"; $mw->Busy(-recurse => 1); open ('FH', '<', 'NewsSurfer.log') || warn "Error - Cannot open NewsSurfer.log\n"; @log = (); close FH; open ('FH', '>', $sf) || warn "Error - Can not save log.\n$!"; foreach my $line (@log) { chomp $line; print FH "$line\n"; } close FH; $mw->Unbusy; return (1); } } sub help #------------------------------------------------------------ { #called from button pressed in the main window $tl6->Popup; $txt_help->focus; $txt_help->delete("1.0", 'end'); $txt_help->insert('end', 'NewsSurfer can download binaries and read messages on '. "usenet newsgroups.\n". "You can use NewsSurfer to post messages and atachments.\n" ); $mw->update; return (1); } sub help_about #------------------------------------------------------ { #called from button pressed in the help window my($pod,); $pod = $mw->Pod(-tree => 0,); $pod->configure(-file => $0); return (1); } sub help_close #------------------------------------------------------ { #called from button pressed in the help window $tl6->withdraw; $mw->update; return (1); } sub quit #------------------------------------------------------------ { #called from button pressed in the main window $mw->Busy(-recurse => 1,); $mw->update; warn 'NewsSurfer has closed. (' . localtime() . ")\n"; $mw->destroy; return (1); } sub raise_rc_menu #--------------------------------------------------- { #called from right-clicking in the main window my $toplevelwidget = $_[0] || 'NULL'; #required my $listbox = $_[1]; #optional my($x, $y) = $mw->pointerxy; my $height; $mw->Busy(-recurse => 1,); if ($listbox && Exists($listbox) && Exists($toplevelwidget)) { #a listbox was supplied my($selected,); #verify widget is a listbox eval { $selected = $listbox->nearest($y - $listbox->rooty) }; if (! $@) { #it's a listbox, make selection at xy if there is a entry if ($selected) { $listbox->selectionClear(); $listbox->selectionSet($selected); } } $height = $toplevelwidget->height; $y -= $height; } if (Exists($toplevelwidget)) { $toplevelwidget->geometry('+'."$x".'+'."$y"); $toplevelwidget->deiconify(); $toplevelwidget->raise(); $toplevelwidget->focus(); $mw->update; } $mw->Unbusy; return (1); } sub lb2_msg_select_all #---------------------------------------------- { #called from the rc menu or key binding my (@paths, $count, $home, $end,); $mw->Busy(-recurse => 1); @paths = $lb2_msg->infoChildren; if (@paths) { $count = $#paths; $home = $paths[0]; $end = $paths[$count]; $lb2_msg->focus; $lb2_msg->selectionSet("$home", "$end"); } $mw->update; $mw->Unbusy; return (1); } sub lb2_msg_select_end #---------------------------------------------- { #called from the rc menu or key binding my ($sel, @paths, $count, $end,); $sel = $lb2_msg->selectionGet; @paths = $lb2_msg->infoChildren; $count = $#paths; $end = $paths[$count]; $lb2_msg->selectionSet($sel, "$end"); $mw->update; return (1); } sub lb2_msg_select_hom #---------------------------------------------- { #called from the rc menu or key binding my ($sel, @paths, $home,); $sel = $lb2_msg->selectionGet; @paths = $lb2_msg->infoChildren; $home = $paths[0]; $lb2_msg->selectionSet($sel, "$home"); $mw->update; return (1); } sub FlashButton #----------------------------------------------------- { #called during widget creation my $w = $_[0]; my $c1 = $_[1]; my $c2 = $_[2]; unless($w and $c1 and $c2) { return (0); } $w->bind('' => sub { $w->configure(-relief => 'flat',); $w->configure(-fg => $c1); $w->flash; $w->flash; $w->configure(-fg => $c2); }); return (1); } sub MainButtons #----------------------------------------------------- { #called during widget creation my $w = $_[0]; my $o1 = $_[1]; my ($id1, $id2, $i1, $i2, $i3,); my $o2 = $o1.'1'; $id1 = load_image($o1); $id2 = load_image($o2); $i1 = $mw->Photo( -data => $id1, -format => 'bmp', -palette => '256', ); $i2 = $mw->Photo( -data => $id1, -format => 'bmp', -palette => '64/64/64', ); $i3 = $mw->Photo( -data => $id2, -format => 'bmp', ); undef $id1; undef $id2; my $width = $mw->screenwidth; if ($width > 800) { $i1->copy($i1, -zoom => 1.9,1,); $i2->copy($i2, -zoom => 1.9,1,); $i3->copy($i3, -zoom => 1.9,1,); } $w->configure(-relief => 'flat', -image => $i1); $w->bind('' => sub { $w->configure(-image => $i1); }); $w->bind('' => sub { $w->configure(-image => $i2); }); $w->bind('' => sub { $w->configure( -relief => 'flat', -image => $i3 ); }); $w->bind('' => sub { $w->configure( -relief => 'flat', -image => $i1 ); }); $mw->update; return (1); } sub ready #----------------------------------------------------------- { while ($pb > 100) { $pb++; if ($pb % 2) { $mw->update }; } $pb = 0; update_status('Ready'); $mw->Unbusy; $mw->update; return (1); } sub error #----------------------------------------------------------- { #called from various subroutines my $error = $_[0] || 'NULL'; my $opt1 = $_[1] || 0; my(@sel, $group,); my $msg = "\nError - "; @sel = $lb1_grp->selectionGet; eval { $group = $lb1_grp->itemCget($sel[0], 0, -text) }; if ($@) { warn "Error - No valid group selected.\n"; } $mw->Busy(-recurse => 1); if ($error eq 'connect') { $msg .= "Could not connect, try again.\n"; } elsif ($error =~ m/ ^grab.* #'grab' followed by anything at the beginning of string /xig) { if ($error eq 'grab_1') { $msg .= "Cannot open $group.dat\n$!"; } elsif ($error eq 'grab_2a') { $msg .= "Cant decode attachment.\n"."File already exists.\n"; } elsif ($error eq 'grab_2b') { $msg .= "Cant decode attachment. Decoder error.\n$!"; } elsif ($error eq 'grab_3') { $msg .= "Cannot open $group.grp\n$!"; } elsif ($error eq 'grab_4') { $msg .= "Error downloading article from server\n$!"; } elsif ($error eq 'grab_4a') { $msg .= "Article does not exist on server\n$!"; } } elsif ($error =~ m/ ^scan.* #'scan' followed by anything at the beginning of string /xig) { if ($error eq 'scan_1') { $msg .= "Must select a group to scan.\n"; } elsif ($error eq 'scan_2') { $msg .= "Cannot open $group.grp\n$!"; } elsif ($error eq 'scan_3') { $msg .= "Cannot create $group.grp\n$!"; } } elsif ($error =~ m/ ^grp.* #'grp' followed by anything at the beginning of string /xig) { if ($error eq 'grp_1') { $msg .= "Cannot open groups.\n$!"; } elsif ($error eq 'grp_update_1') { $msg .= "Can't create local groups.\n$!"; } elsif ($error eq 'grp_unsub_1') { $msg .= "Unable to delete $_[1].grp\n$!"; } elsif ($error eq 'grp_unsub_2') { $msg .= "Unable to delete $_[1].dat\n$!"; } elsif ($error eq 'grp_search_1') { $msg .= "Must enter a search term.\n"; } } elsif ($error =~ m/ ^post.* #'post' followed by anything at the beginning of string /xig) { if ($error eq 'post_1') { $msg .= "Must scan a group to post to.\n"; } elsif ($error eq 'post_atch_1') { $msg .= "Cannot open file\n$!"; } elsif ($error eq 'post_attach_tobig') { $msg .= "Can't post file attachments larger than 1Mb.\n"; } } else { if ($error eq 'login') { $msg .= "Cannot login\n$!"; } elsif ($error eq 'MLDBM') { $msg .= "Error opening .grp database file\n$!"; } elsif ($error eq 'conf_browse1') { $msg .= 'Manually enter path or upgrade perl/Tk.'; } elsif ($error eq 'msg_del_1') { $msg .= "Cannot open $group.grp\n$!"; } elsif ($error eq 'read_1') { $msg .= "Cannot open $group.grp\n$!"; } elsif ($error eq 'rset_1') { $msg .= "Unable to delete file.\n$!"; } elsif ($error eq 'sort_1') { $msg .= "Cannot open $group.grp\n$!"; } elsif ($error eq 'sig1') { $msg .= "Can't load signature.\n$!"; } } #display error chomp ($msg); warn "$msg\n"; update_status($msg); $mw->after(2000); #check if die was requested if ($opt1) { $mw->destroy; } else { ready(); return (1); } return (0); } sub update_status #---------------------------------------------------<-redo interface { #called from various subroutines #called with either a status message in $txt #or a number followed by PROGRESSBAR (triggers progressbar update) #or a word followed by PROGRESSBAR (triggers progress complete) my $txt = $_[0] || 'NULL'; my $opt = $_[1] || 'NULL'; if ($opt eq 'PROGRESSBAR') { if ($txt =~ m/(^\d+)/) { #update status of progressbar for (1..$1) { $pb++; $mw->update; $mw->after(64); } } else { #complete and reset the progress bar while ($pb < 100) { $pb++; $mw->update; } $pb = 0; } } elsif ($txt ne 'NULL' && $opt ne 'PROGRESSBAR') { $sb_lab->configure(-text => " $txt"); $mw->update; } else { warn "Error - update_status error\ntxt [$txt]\nopt [$opt]\n$!"; $mw->destroy; } return (1); } return (1); } #***********************************************************************END news_gui sub load_image #-------------------------------------------------------- { #called from various subroutines, returns imagedata my $opt = $_[0]; my $imagedata; if ($opt == 2) { $imagedata = 'Qk1YAgAAAAAAADYAAAAoAAAACgAAABEAAAABABgAAAAAAAAAAAASCwAAEgsAAA'. 'AAAAAAAAAA////////////////////////////////////////AAD4+Pj/////'. '//////////////////////////////8AAP////////////////////////////'. '39/f39/fj4+AAA////////////////////////////////////////AAD/////'. '///+/v7+/v79/f39/f3+/v7///////////8AAGZHumlKvUYvi4p0xotyzVhEkm'. 'BFp1w8sP38/v///wAAdlfK////////9fL79fP8+vj//fz/i3LN+vn9////AABm'. 'R7r49//4+/9JTFsXGCGvr7b///9sU7X////9/f8AAGZHuvHu/sPG1RYZKBobKx'. 'AQFGFedGFHrPz8/f79/wAAa0+6/Pv/Xl5mAAAIq6yyJygrAAASZE6i+/v9/v7/'. 'AAByWLv49f/R0NfLy9Dw8PStrrUHBhBRP4P///////8AAHNVv/v6/fXy+vj3/P'. 'Lv+vHv+RcSJgUDCO/v7////wAAgGTGeV3AlX3RhGjLi3LNi3LNdlrAIiElDQsT'. '7+/vAAD///3///7+/f/////////////9/fz39/clJyPQ0NAAAP////////////'. '///////////////////////////wAA/v7+////////////////////////+/v7'. '////+Pj5AAD////////+/v7+/v7+/v7////////////////7/PsAAAAA'; } elsif ($opt == 3) { $imagedata = 'Qk1YAgAAAAAAADYAAAAoAAAACgAAABEAAAABABgAAAAAAAAAAAASCwAAEgsAAA'. 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBwcAAAAA'. 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. 'ICAgICAgcHBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. 'AAARDSABAQEBAQEAAAAAAAANCRgAAAAAAAAAAGZHunZXymtQtmZHumlYmHNfqm'. 'tRsWxSsAgHDQAAAAAAdlfKAAAAAAAACg0ECgwDBQcAAgMAZke6HBYuAAAAAABm'. 'R7oHCAAHBAC2s6To595QUEkAAABtWKgZES0BAQAAAGZHug4RATw5Kunm1+Xk1O'. '/v656hi3JepREMHQAAAAAAa0+6AwQAoaGZ///3VFNN2NfU///taUy1GRIsAQEA'. 'AAByWLsHCgAjIx4qKiYODgpSUUr4+e9sUrQYESwAAAAAAHNVvwoJDg0KFRwUMB'. 'wYKBQTEdDO0/r89xAQEAAAAAAAbU69Zke6ZkytZ0uzaVSgZke6Zke63d7a8vTs'. 'EBAQAAAAAAIAAAEBAgAAAAAAAAAAAAACAgMICAja2NwvLy8AAAAAAAAAAAAAAA'. 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAAAAAAAAAAAAAAAAAAAAAAAABAQE'. 'AAAABwcGAAAAAAAAAAABAQEBAQEBAQEAAAAAAAAAAAAAAAAEAwQAAAAA'; } elsif ($opt == 10) { $imagedata = 'Qk2mDgAAAAAAADYAAAAoAAAAOAAAABYAAAABABgAAAAAAHAOAAAmDgAAJg4AAAAAAAAAAAAAoaGh oaGhoKCgoKCgoKCgnJyckJCQiIiIh4eHh4eHhYWFhYWFhISEhISEhISEg4ODg4ODg4ODg4ODgoKC goKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg4ODg4ODg4ODhISEhISEhISEhYWFhYWFhoaGhoaG hoaGh4eHh4eHh4eHh4eHh4eHiIiIiIiIiIiIjIyMlJSUnp6eoaGhoaGhoaGhoaGhoaGhoaGhoaGh oKCgoKCgkpKSj4+Purq61NTU29vb2dnZ2tra2dnZ2NjY1tbW1tbW1dXV1dXV1dXV1NTU1NTU1NTU 09PT09PT09PT09PT09PT09PT09PT1NTU1dXV1dXV1dXV1tbW1tbW2NjY2dnZ2tra29vb3Nzc3Nzc 3Nzc3d3d3d3d3t7e3t7e39/f39/f39/f0dHRsrKyioqKlJSUoaGhoaGhoaGhoaGhoaGhoaGhoKCg jo6OoqKi6enp/Pz8+/v7+vr6+fn5+Pj49vb29fX19PT08/Pz8vLy8fHx8fHx8PDw7+/v7+/v7+/v 7+/v7+/v7u7u7+/v7+/v7+/v8PDw8PDw8fHx8vLy8/Pz9PT09fX19/f3+Pj4+fn5+vr6+/v7/Pz8 /f39/f39/v7+/v7+////////////////////5ubmoaGhjo6OoaGhoaGhoaGhoaGhoKCgkZGRoKCg +fn5/Pz8+/v7+vr6+fn5+Pj49vb29fX18/Pz8vLy8fHx8PDw7+/v7u7u7u7u7e3t7Ozs7Ozs7Ozs 7Ozs7Ozs7Ozs7Ozs7Ozs7u7u7u7u7+/v8PDw8fHx8vLy8/Pz9fX19/f3+Pj4+fn5+vr6+/v7/Pz8 /f39/v7+/v7+/v7+/////////////////////Pz8qqqqj4+PoaGhoaGhoaGhmZmZi4uL5+fn9vb2 9fX19PT08/Pz8vLy8fHx7+/v7e3t6+vr6urq6enp6Ojo5ubm5ubm5eXl5OTk5OTk5OTk5OTk5OTk 5OTk5OTk5OTk5OTk5eXl5ubm5ubm6Ojo6enp6urq7Ozs7u7u7+/v8fHx8/Pz9PT09fX19vb29/f3 9/f3+Pj4+Pj4+fn5+fn5+fn5+fn5+fn5+fn58/PznJyck5OToaGhoaGhjIyMxcXF7+/v7u7u7e3t 7Ozs6+vr6enp6Ojo5ubm5eXl4+Pj4eHh39/f3t7e3d3d3Nzc3Nzc29vb29vb2tra2tra2tra2tra 2tra2tra29vb3Nzc3d3d3d3d39/f4eHh4uLi4+Pj5eXl5+fn6Ojo6urq6+vr7e3t7u7u7u7u7+/v 8PDw8PDw8fHx8fHx8fHx8fHx8fHx8fHx8fHx4ODgh4eHn5+fnJycjo6O4uLi5eXl5OTk4+Pj4uLi 4eHh39/f3d3d29vb2dnZ2NjY1tbW1NTU09PT0tLS0dHR0NDQ0NDQz8/Pz8/Pz8/Pz8/Pz8/Pz8/P z8/P0NDQ0dHR0tLS09PT1NTU1dXV19fX2dnZ2tra3Nzc3t7e4ODg4uLi4uLi4+Pj5OTk5eXl5ubm 5ubm5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fnp6enlZWVlJSUpKSk29vb2tra2dnZ2NjY19fX1dXV 09PT0tLS0NDQzs7OzMzMy8vLycnJyMjIx8fHxsbGxsbGxcXFxMTExMTExMTEw8PDw8PDxMTExMTE xcXFxsbGxsbGx8fHycnJysrKzMzMzc3Nz8/P0dHR09PT1dXV1tbW2NjY2dnZ2dnZ2tra29vb29vb 3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzcxMTEiYmJjIyMsrKyzs7Ozs7Ozc3NzMzMy8vLycnJyMjI xsbGxcXFw8PDwcHBv7+/vr6+vLy8vLy8u7u7urq6ubm5ubm5uLi4uLi4uLi4uLi4uLi4ubm5ubm5 u7u7vLy8vLy8vb29v7+/wMDAwsLCxMTExcXFx8fHycnJysrKzMzMzc3Nzs7Ozs7Oz8/Pz8/P0NDQ 0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQy8vLgoKChoaGt7e3wsLCwsLCwcHBwMDAv7+/v7+/vr6+vb29 vLy8u7u7uLi4t7e3tbW1vaem32Vf8kI48kI46lBH0Xp1sbGxsbGx1nNt7kk/8kI45VdOsrKy419W 8kI45lhP8kI4419XuLi48kI45GBYvb295WFZ8kI4wcHBwsLCw8PDxMTExMTExcXFxcXFxsbGxcXF xcXFxMTExMTExMTExMTExMTEwMDAgoKCg4ODs7OzuLi4t7e3tra2tra2urq6u7u7u7u7urq6ubm5 t7e3tbW1tLS0s7OzwpqX6FxTyY2JwZiW1Hp08FBGyIyI0IF78FBG1Hp0wJiW1Hp0wZiW8FBG3W9o xpSQ8FBG4WpitbW18FBG4mtjurq642tk8FBGvb29vr6+v7+/wMDAwcHBwcHBwcHBwsLCwsLCwMDA vLy8ubm5ubm5ubm5ubm5tra2goKCgoKCq6urrq6urq6urKyssrKyurq6u7u7urq6ubm5t7e3t7e3 tra2tLS0tLS0t66us7Ozs7Ozwp2b3Xhy8F5V0IiE4HNt8F5VsbGxsrKysrKyt62t7GRb6Glh0omF 8F5V4XRttra28F5V4nRuubm54nVv8F5VvLy8vb29vb29vr6+v7+/v7+/v7+/v7+/v7+/v7+/u7u7 srKyr6+vr6+vr6+vrKysgoKCg4ODoqKipqampqamp6entbW1vr6+vr6+vb29vLy8u7u7urq6urq6 ubm5uLi4t7e305SQ7XRt8HBo8HBo7XRtva2s3oaB8HBovq6tt7e3vq6ut7e3u7OyyqKf1JSR8HBo 4oJ8urq68HBo44N9vLy85IN+8HBov7+/v7+/wMDAwcHBwcHBwcHBwcHBwcHBwcHBwcHBwMDAs7Oz qKiop6enp6enpaWlgoKCh4eHmZmZoKCgoKCgp6envb29xMTExMTEw8PDw8PDwcHBwcHBwcHBwMDA wMDAybSy8oN974eB252a0qimwbu6vr6+yLSy74eB74eB5ZKN8oN9vr6+4paS5ZKN6I+J8oN93J6b wcHB8oN974eB5pOO8oN97IuGxMTExMTExcXFxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGurq6paWl oaGhoaGhn5+fgoKCjY2Nk5OToKCgoKCgrq6uysrKz8/Pzs7Ozs7Ozs7Ozc3NzMzMzMzMzMzMy8vL 08LB9JmT56ily8vLzcfH2ri20sHAysrK0MTD3bWy37Kv2Lu5y8vL08LB4LKw4LKw27m2zMzMzMzM 18C+1cPC18C/4bSx08jHz8/Pz8/Pz8/P0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQx8fHq6uroaGh oaGhn5+fgoKClZWVi4uLoKCgoKCgtLS01NTU2dnZ2dnZ2NjY2NjY19fX19fX19fX19fX1tbW1tbW 6MC99q6p9q6p9q6p9q6p3M7N1tbW1tbW1tbW1tbW1tbW1tbW1tbW19fX19fX19fX19fX19fX19fX 2NjY2NjY2NjY2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2tra2tra2tra2tra2tra0dHRr6+voaGhoaGh l5eXioqKnp6eg4ODnp6eoKCgsLCw2NjY4uLi4uLi4uLi4uLi4uLi4eHh4eHh4eHh4eHh4eHh4eHh 4t/f59nY59nY4eHh4ODg4ODg4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4uLi 4uLi4uLi4uLi4uLi4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj0tLSq6uroaGhoaGhioqK l5eXoaGhkJCQkJCQoKCgqamp1tbW6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6urq6urq 6urq6urq6urq6urq6urq6urq6urq6urq6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr 6+vr6+vr6+vr7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs6+vry8vLpaWloaGhmZmZgYGBoKCg oaGhnZ2dgYGBmZmZoqKixsbG7u7u8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz 8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz 8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz9PT09PT09PT09PT09PT05+fnubm5oaGhnZ2dhYWFlZWVoaGhoaGh oaGhlJSUhYWFnZ2dsbGx4eHh+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6 +vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6 +vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr6+vr68/Pzzs7Op6ennp6eiIiIkZGRoaGhoaGhoaGhoaGh oaGhkJCQhISEmpqavLy84ODg8vLy9/f39/f3+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5 +fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5 +fn5+fn5+fn5+fn5+fn5+fn58/Pz5eXly8vLrKyslpaWhYWFkJCQoaGhoaGhoaGhoaGhoaGhoaGh oaGhlpaWgYGBjIyMoaGhsrKyurq6vb29wMDAwcHBwcHBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcHB wcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB wcHBwcHBwcHBwcHBwMDAtra2pqamlpaWioqKgICAmZmZoaGhoaGhoaGhoaGh'; } elsif ($opt == 101) { $imagedata = 'Qk2mDgAAAAAAADYAAAAoAAAAOAAAABYAAAABABgAAAAAAHAOAAAmDgAAJg4AAAAAAAAAAAAAoaGh oaGhoKCgoKCgoKCgnJyckJCQiIiIh4eHh4eHhYWFhYWFhISEhISEhISEg4ODg4ODg4ODg4ODgoKC goKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg4ODg4ODg4ODhISEhISEhISEhYWFhYWFhoaGhoaG hoaGh4eHh4eHh4eHh4eHh4eHiIiIiIiIiIiIjIyMlJSUnp6eoaGhoaGhoaGhoaGhoaGhoaGhoaGh oKCgoKCgkpKShoaGmpqapaWlqampqKioqKiop6enp6enpaWlpaWlpaWlpKSkpKSko6Ojo6Ojo6Oj o6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6OjpKSkpKSkpaWlpaWlpaWlp6enp6enqKioqampqampqqqq qqqqq6urq6urq6urq6urrKysrKysrKyspqaml5eXhYWFlJSUoaGhoaGhoaGhoaGhoaGhoaGhoKCg jo6Oj4+PsLCwubm5uLi4t7e3t7e3tra2tLS0tLS0s7OzsrKysbGxsbGxsbGxsLCwr6+vr6+vr6+v r6+vr6+vr6+vr6+vr6+vr6+vsLCwsLCwsbGxsbGxsrKys7OztLS0tbW1tra2t7e3t7e3uLi4ubm5 urq6urq6urq6urq6u7u7u7u7u7u7u7u7u7u7r6+vkJCQjo6OoaGhoaGhoaGhoaGhoKCgkZGRj4+P uLi4ubm5uLi4t7e3t7e3tra2tLS0tLS0srKysbGxsbGxsLCwr6+vr6+vr6+vrq6ura2tra2tra2t ra2tra2tra2tra2tra2tr6+vr6+vr6+vsLCwsbGxsbGxsrKytLS0tbW1tra2t7e3t7e3uLi4ubm5 urq6urq6urq6urq6u7u7u7u7u7u7u7u7u7u7urq6lJSUj4+PoaGhoaGhoaGhmZmZhISEsLCwtra2 tbW1tLS0tLS0s7OzsrKysbGxr6+vrq6ura2trKysrKysqqqqqqqqqqqqqampqampqampqKioqKio qKioqKioqampqampqqqqqqqqqqqqrKysrKysra2tr6+vsLCwsbGxsrKytLS0tLS0tbW1tra2t7e3 t7e3t7e3t7e3uLi4uLi4uLi4uLi4uLi4uLi4tbW1jY2Nk5OToaGhoaGhjIyMnp6esrKysbGxsLCw r6+vr6+vra2trKysq6urqqqqqKiop6enpqampaWlpKSkpKSkpKSko6Ojo6OjoqKioqKioqKioqKi oqKioqKio6OjpKSkpKSkpKSkpqamp6enqKioqKioqqqqq6urra2trq6ur6+vsLCwsbGxsbGxsrKy srKysrKys7Ozs7Ozs7Ozs7Ozs7Ozs7