########################################################################## # # File : mpx.irc # Ver : 1.0beta # Desc : MP3 Leecher # Auth : blue-elf / astigmatik # Email: j3pr0x@gmail.com # Date : 28 March 2006 (25 March 2007 by `butane, fixed b41 compatability) # ########################################################################## # # How to install: # Type inside jIRCii /load mpx.irc # # How to use: # Type /mpx # Click "Options" button. Add your favorite MP3 channels. # Connect to your network/server where you get your MP3s. # Type search word, click "Find" button. # When results come out, just select one and click "Request" button. # Everything else should be self-explanatory. # # Contact me at email above or join #jircii in efnet. # # Updates (28 March 2006) # - Fixed resume button (I think) # - Query windows won't come out now. # - Added sections (Status, Transfer, etc) # ######################################################################### import java.awt.*; import javax.swing.*; import javax.swing.table.*; import javax.swing.border.*; sub mpxSeeker { if ($mpxWin) { [$mpxWin show]; return; } $mpxWin = [new JFrame: "MP3 Seeker"]; [$mpxWin setIconImage:[[new ImageIcon: getProperty('jirc.icon')] getImage]]; #[$mpxWin setDefaultLookAndFeelDecorated: true]; [$mpxWin setLocation: 100, 100]; [&mpxForceSize: $mpxWin, 670, 550]; [$mpxWin setVisible: 1]; [$mpxWin setDefaultCloseOperation: [JFrame DISPOSE_ON_CLOSE]]; #[$mpxWin addWindowListener: lambda({ # echo("Width ".[$mpxWin getWidth]); # echo("Height ".[$mpxWin getHeight]); # echo([$mpxWin getLocation]);})]; #[$mpxWin setAlwaysOnTop: 1]; # Main panel $mpxm = [$mpxWin getContentPane]; [$mpxm setLayout:[new BorderLayout: 0,5]]; [$mpxm setBorder:[BorderFactory createEmptyBorder: 5, 5, 3, 5]]; # Search Pane $mpxs = [new JPanel]; [$mpxm add: $mpxs, [BorderLayout NORTH]]; [$mpxs setBorder: [BorderFactory createEmptyBorder: 0, 2, 0, 3]]; # top, left, bottom, and right [$mpxs setLayout:[new BorderLayout: 5, 5]]; [$mpxs add:[new JLabel:"Search for: "], [BorderLayout WEST]]; $mpxsearchField = [new JTextField]; [$mpxs add: $mpxsearchField, [BorderLayout CENTER]]; $mpxsearchBtn = [new JButton:"Find"]; [&mpxStyleBtn: $mpxsearchBtn]; [$mpxs add: $mpxsearchBtn, [BorderLayout EAST]]; [$mpxsearchBtn addActionListener: lambda({ mpxFind(); })]; # Pane for stats/results to be divided into 3 rows using GridLayout $mpxr = [new JPanel]; [$mpxm add: $mpxr, [BorderLayout CENTER]]; # Divide $r Panel into three panels [$mpxr setLayout:[new GridLayout: 3, 1, 0, 1]]; # p.setLayout(new GridLayout(rows, cols, hgap, vgap)) # Status Window $mpxstatus = [new JPanel]; [$mpxstatus setLayout: [new BorderLayout: 5, 0]]; [$mpxstatus setBorder: mpxPanelBorder(" Status ")]; $mpxstatusWindow = [new JTextArea]; [$mpxstatusWindow setEditable: 0]; # These two have to be set together so that lines are wrapped by whole words. [$mpxstatusWindow setLineWrap: 1]; [$mpxstatusWindow setWrapStyleWord: 1]; [$mpxstatusWindow setFont: mpxFontStyle()]; [$mpxstatusWindow setBackground: [$mpxWin getBackground]]; $mpxstatusWPanel = [new JPanel]; [$mpxstatusWPanel setLayout:[new FlowLayout: [FlowLayout CENTER], 0, 20]]; # new FlowLayout(int align, int hgap, int vgap) [$mpxstatusWPanel setBorder:[BorderFactory createEmptyBorder:0,0,0,0]]; [$mpxstatus add: [new JScrollPane: $mpxstatusWindow], [BorderLayout CENTER]]; [$mpxstatus add: $mpxstatusWPanel, [BorderLayout EAST]]; $mpxClearStatus = lambda({ [$mpxstatusWindow setText: ""]; }); $mpxstatusClrBtn = [new JButton:"Clear"]; [&mpxStyleBtn: $mpxstatusClrBtn]; [$mpxstatusClrBtn addActionListener: $mpxClearStatus]; [$mpxstatusWPanel add: $mpxstatusClrBtn]; # Results Pane $mpxresults = [new JPanel]; [$mpxresults setLayout: [new BorderLayout: 5, 0]]; [$mpxresults setBorder: mpxPanelBorder(" Results ")]; $mpxtModel = [new DefaultTableModel]; $mpxresultTable = [new JTable: $mpxtModel]; [$mpxtModel addColumn:"Trigger"]; [$mpxtModel addColumn:"Channel"]; [[$mpxresultTable getTableHeader] setReorderingAllowed: false]; $mpx_ResultTableFiller = lambda({ local('$x'); for ($x = 0; $x < 50; $x++) { [$mpxtModel addRow: array('','')]; } }); [$mpx_ResultTableFiller]; [$mpxresults add: [new JScrollPane: $mpxresultTable], [BorderLayout CENTER]]; $mpxresultPanel = [new JPanel]; [$mpxresults add: $mpxresultPanel, [BorderLayout EAST]]; [$mpxresultPanel setBorder:[BorderFactory createEmptyBorder:0,0,0,0]]; [$mpxresultPanel setLayout: [new BoxLayout: $mpxresultPanel, [BoxLayout Y_AXIS]]]; $mpxbtnClrResult = [new JButton:"Clear list"]; [&mpxStyleBtn: $mpxbtnClrResult]; $mpx_ClearRList = lambda({ local('$x'); while ([$mpxtModel getRowCount]) { [$mpxtModel removeRow: 0]; } [$mpx_ResultTableFiller]; }); [$mpxbtnClrResult addActionListener: $mpx_ClearRList]; $mpxbtnRequest = [new JButton:"Request"]; [&mpxStyleBtn: $mpxbtnRequest]; [$mpxbtnRequest addActionListener: lambda({ mpxRequest(); })]; [$mpxresultPanel add:[Box createRigidArea:[new Dimension: 0, 30]]]; [$mpxresultPanel add: $mpxbtnClrResult]; [$mpxresultPanel add:[Box createRigidArea:[new Dimension: 0, 5]]]; [$mpxresultPanel add: $mpxbtnRequest]; # DCC Pane $mpxrequest = [new JPanel]; [$mpxrequest setLayout:[new BorderLayout: 5, 0]]; [$mpxrequest setBorder: mpxPanelBorder(" Transfers ")]; $mpxrTableModel = [new DefaultTableModel]; $mpxrTable = [new JTable: $mpxrTableModel]; [$mpxrTableModel addColumn:"File"]; [$mpxrTableModel addColumn:"Nick"]; [$mpxrTableModel addColumn:"Channel"]; [$mpxrTableModel addColumn:"Status"]; [[$mpxrTable getTableHeader] setReorderingAllowed: false]; for ($x = 0; $x < 100; $x++) { [$mpxrTableModel addRow: array('','')]; } [$mpxrequest add:[new JScrollPane: $mpxrTable], [BorderLayout CENTER]]; $mpxrequestBtnHolder = [new JPanel]; [$mpxrequestBtnHolder setLayout: [new BoxLayout: $mpxrequestBtnHolder, [BoxLayout Y_AXIS]]]; [$mpxrequest add: $mpxrequestBtnHolder, [BorderLayout EAST]]; $mpxbtnRemoveQ = [new JButton:"Remove"]; [&mpxStyleBtn: $mpxbtnRemoveQ]; $mpx_RemoveQ = lambda({ local('$f $n $c $s $r $str'); $r = [$mpxrTable getSelectedRow]; if ($r < 0 || $r eq $null) { return; } $f = [$mpxrTableModel getValueAt: $r, 0]; $n = [$mpxrTableModel getValueAt: $r, 1]; $c = [$mpxrTableModel getValueAt: $r, 2]; $s = [$mpxrTableModel getValueAt: $r, 3]; if ("RECEIVE COMPLETE" isin $s) { [$mpxrTableModel removeRow: $r]; } else { $str = "Remove the following from the list? \n\nFile\: $f \nNick\: $n \nChannel\: $c"; if (mpxYesNo($mpxWin, $str, "MP3 Cancel download") eq "YES") { [$mpxrTableModel removeRow: $r]; sendMessage($c,"@$n $+ -remove $f"); call("/dcc close $n"); } } }); [$mpxbtnRemoveQ addActionListener: $mpx_RemoveQ]; $mpxbtnResume = [new JButton:"Resume"]; [&mpxStyleBtn: $mpxbtnResume]; $mpx_Resume = lambda({ local('$f $n $c $s $r $str'); $r = [$mpxrTable getSelectedRow]; if ($r < 0 || $r eq $null) { return; } $f = [$mpxrTableModel getValueAt: $r, 0]; $n = [$mpxrTableModel getValueAt: $r, 1]; $c = [$mpxrTableModel getValueAt: $r, 2]; $s = [$mpxrTableModel getValueAt: $r, 3]; if ("RECEIVE FAIL" !isin $s) { return; } sendMessage($c, "!$n $f"); mpxEcho("Attempting to resume $f from $n"); }); [$mpxbtnResume addActionListener: $mpx_Resume]; [$mpxrequestBtnHolder add:[Box createRigidArea:[new Dimension: 0, 30]]]; [$mpxrequestBtnHolder add: $mpxbtnRemoveQ]; [$mpxrequestBtnHolder add:[Box createRigidArea:[new Dimension: 0, 5]]]; [$mpxrequestBtnHolder add: $mpxbtnResume]; [$mpxr add: $mpxstatus]; [$mpxr add: $mpxresults]; [$mpxr add: $mpxrequest]; # Pane for Options/Close buttons $mpxl = [new JPanel]; [$mpxl setLayout:[new FlowLayout: [FlowLayout CENTER], 5, 5]]; [$mpxm add: $mpxl, [BorderLayout SOUTH]]; $mpxoptionsB = [new JButton:"Options..."]; [&mpxForceSize: $mpxoptionsB, 90, 25]; [$mpxoptionsB addActionListener: &mpxopt]; [$mpxoptionsB setFont: mpxFontStyle()]; $mpxclose = [new JButton:"Close"]; [&mpxForceSize: $mpxclose, 90, 25]; [$mpxclose setFont: mpxFontStyle()]; [$mpxclose addActionListener: lambda({ [$mpxWin dispose]; mpxCleanUp(); }, $mpxWin => $mpxWin)]; [$mpxl add: $mpxoptionsB]; [$mpxl add: $mpxclose]; [$mpxWin pack]; [$mpxWin show]; } sub mpxPanelBorder { local('$empty $matte $combo $title'); $empty = [BorderFactory createEmptyBorder: 2, 0, 7, 0]; $matte = [BorderFactory createMatteBorder: 1,0,0,0, [Color GRAY]]; $combo = [BorderFactory createCompoundBorder: $empty, $matte]; $title = [new TitledBorder: $combo, $1]; #[$title setTitleJustification: [TitledBorder CENTER]]; [$title setTitleFont: [new Font: "Verdana", [Font BOLD], 11]]; return $title; } sub mpxFontStyle { local('$fontDefault @fontList $font'); $fontDefault = "sansserif"; @fontList = [[java.awt.GraphicsEnvironment getLocalGraphicsEnvironment] getAvailableFontFamilyNames]; foreach $font (@fontList) { if ($font eq "MS Sans Serif" || $font eq "Microsoft Sans Serif") { $fontDefault = $font; } } return [new Font: $fontDefault, [Font PLAIN], 11]; } sub mpxForceSize { [$1 setPreferredSize:[new Dimension: $2, $3]]; [$1 setMinimumSize:[new Dimension: $2, $3]]; [$1 setMaximumSize:[new Dimension: $2, $3]]; } sub mpxStyleBtn { [$1 setFont: mpxFontStyle()]; [&mpxForceSize: $1, 75, 25]; } alias mpx { mpxSeeker(); } sub mpxCleanUp { $mpxWin = $null; return; } sub mpxEcho { if (!$mpxWin) { return; } [$mpxstatusWindow append: "\[".timeStamp()."\] $1 $+ \n"]; [$mpxstatusWindow setCaretPosition:[[$mpxstatusWindow getDocument] getLength]]; # textArea.setCaretPosition(textArea.getDocument().getLength()); } sub mpxFind { local('$str'); $str = [$mpxsearchField getText]; if (getPropertyList('mpx.channels')[0] ne '' && $str ne '') { mpx_SleepSeemsBroken("$str"); push(@mpxSearchString, "*".strrep(lc($str),' ','*')."*"); } } sub mpx_SleepSeemsBroken { local('$c'); foreach $c (getPropertyList('mpx.channels')) { if ($me ison "$c") { sendMessage($c, "@find $1"); } else { $temp = lc($c); %mpxSearchOnJoin["$temp"] = $1; # This breaks if I put %mpxSearchOnJoin[lc($c)] = 1; sendRaw("JOIN $c"); mpxEcho("* Joining $c"); } } if ($c) { mpxEcho("* Searching for $1 ..."); } } sub mpxRequest { local('$str $chan'); $str = [$mpxresultTable getValueAt: [$mpxresultTable getSelectedRow], 0]; $chan = [$mpxresultTable getValueAt: [$mpxresultTable getSelectedRow], 1]; if ($str && $chan && $me ison $chan) { local('$file $user $stat $temp'); $temp = tokenize($str); $file = getTokenFrom($temp, 1); $user = getToken($temp, 0); $user = substr($user, 1, strlen($user)); $stat = "WAITING SINCE ".timeStamp(); [$mpxrTableModel insertRow: 0, array($file, $user, $chan, $stat)]; sendMessage($chan, $str); } } sub mpxopt { if ($mpxOptions) { [$mpxOptions show]; return; } $mpxOptions = [new JFrame: "MP3 Seeker Options"]; [$mpxOptions setIconImage:[[new ImageIcon: getProperty('jirc.icon')] getImage]]; [$mpxOptions setSize: 238, 307]; [$mpxOptions setLocationRelativeTo: $null]; [$mpxOptions setLayout: $null]; [$mpxOptions setResizable: false]; [$mpxOptions setDefaultCloseOperation: [JFrame DISPOSE_ON_CLOSE]]; $mpxOptCont = [$mpxOptions getContentPane]; $mpxOptStr = [new JLabel:"Search in these channels:"]; [$mpxOptStr setBounds: 12, 12, 130, 16]; $mpxLModel = [new DefaultListModel]; $mpxOptList = [new JList: $mpxLModel]; [$mpxOptList setSelectionMode:[DefaultListSelectionModel SINGLE_SELECTION]]; $mpxOptListScroll = [new JScrollPane: $mpxOptList]; [$mpxOptListScroll setBounds: 12, 32, 142, 110]; [$mpxOptCont add: $mpxOptListScroll]; map({ [$mpxLModel add: [[$mpxOptList getModel] getSize], $1]; }, getPropertyList('mpx.channels')); $mpxOptAddAction = lambda({ local('$var'); $var = mpxInput($mpxOptions, "Enter an MP3 channel", "MP3 Seeker"); if ($var) { [$mpxLModel add: [$mpxLModel getSize], "$var"]; } }); $mpxOptAdd = [new JButton: "Add.."]; [$mpxOptAdd setBounds: 160, 34, 66, 24]; [$mpxOptAdd addActionListener: $mpxOptAddAction]; $mpxOptRem = [new JButton: "Rem"]; [$mpxOptRem setBounds: 160, 62, 66, 24]; $mpxOptRemAction = lambda({ local('$pos'); $pos = [$mpxOptList getSelectedIndex]; if ($pos >= 0) { [$mpxLModel remove: $pos]; } }); [$mpxOptRem addActionListener: $mpxOptRemAction]; $mpxOptLimit = [new JCheckBox:"Limit search results to"]; [$mpxOptLimit setBounds: 14,158,134,20]; $mpxOptEditLim = [new JTextField]; [$mpxOptEditLim setBounds: 157,158,30,22]; $mpxOptHideChan = [new JCheckBox:"Hide channel windows"]; [$mpxOptHideChan setBounds:14,184,140,20]; $mpxOptHideQuery = [new JCheckBox: "Hide query windows"]; [$mpxOptHideQuery setBounds:14,210,140,20]; $mpxOptOK = [new JButton:"Ok"]; [$mpxOptOK setBounds: 38, 250, 74, 24]; [$mpxOptOK addActionListener: lambda({ mpxOptSaveOnClose(); })]; $mpxOptCancel = [new JButton:"Cancel"]; [$mpxOptCancel setBounds: 118, 250, 74, 24]; [$mpxOptCancel addActionListener: lambda({ [$mpxOptions dispose]; })]; [$mpxOptLimit setSelected: int(getProperty('mpx.limitToggle'))]; [$mpxOptEditLim setText: getProperty('mpx.limitNumber')]; [$mpxOptHideChan setSelected: int(getProperty('mpx.hideChan'))]; [$mpxOptHideQuery setSelected: int(getProperty('mpx.hideQueries'))]; map({ [$mpxOptCont add: $1]; [$1 setFont: mpxFontStyle()]; }, array($mpxOptLimit, $mpxOptEditLim, $mpxOptHideChan, $mpxOptHideQuery, $mpxOptAdd, $mpxOptRem, $mpxOptStr, $mpxOptOK, $mpxOptCancel)); [$mpxOptions show]; } sub mpxOptSaveOnClose { local('$total $x @temp'); $total = [$mpxLModel getSize]; for ($x = 0; $x < $total; $x++) { push(@temp, [$mpxLModel getElementAt: $x]); } setPropertyList('mpx.channels', @temp); setProperty('mpx.hideQueries', [$mpxOptHideQuery isSelected]); setProperty('mpx.limitToggle', [$mpxOptLimit isSelected]); setProperty('mpx.limitNumber', [$mpxOptEditLim getText]); [$mpxOptions dispose]; } # $var = mpxInput(component, String Message, Title); sub mpxInput { import javax.swing.JOptionPane; local('$parent $string $title'); if ($3 eq $null) { $parent = $null; $string = $1; $title = $2; } else { $parent = $1; $string = $2; $title = $3; } return [JOptionPane showInputDialog: $parent, $string, $title, [JOptionPane QUESTION_MESSAGE]]; } # $var = mpxYesNo(component, String Message, Title) sub mpxYesNo { import javax.swing.JOptionPane; local('$parent $string $title $result'); if ($3 eq $null) { $parent = $null; $string = $1; $title = $2; } else { $parent = $1; $string = $2; $title = $3; } $result = [JOptionPane showConfirmDialog: $parent, $string, $title, [JOptionPane YES_NO_OPTION]]; return iff($result, "NO", "YES"); } on join { if ($nick ne $me || !$mpxWin) { return; } local('$key $var'); $key = lc($target); $var = %mpxSearchOnJoin["$key"]; if ($var) { sendMessage($target, "@find $var"); remove(%mpxSearchOnJoin, $key); } } sub mpxInMP3Chan { local('$n @p $r'); $r = 0; @p = getPropertyList('mpx.channels'); if (size(@p) == 0) { return 0; } foreach $c (@p) { if ($1 ison $c) { $r = 1; break; } } return $r; } on notice { if ($mpxWin && $target eq $me && mpxInMP3Chan($source)) { mpxEcho("- $nick - " . strip($parms)); } } on msg { if (!$mpxWin || mpxInMP3Chan($source) == 0) { return; } local('$v $r'); $v = strip($parms); $r = '(?i).*?(\!\S+.*\.mp3).*'; #$r2 = '(?i)(!.*\.mp3).*'; if ($v ismatch $r) { local('$str $chan $rows'); $str = matched()[0]; $chan = getChannels($nick)[0]; if (mpxStrMatch($str)) { [$mpxtModel insertRow: 0, array($str, $chan)]; $rows = [$mpxtModel getRowCount] - 1; if ([$mpxtModel getValueAt: $rows, 0] eq '') { [$mpxtModel removeRow: $rows]; } } } if (getProperty('mpx.hideQueries') == 1) { local('$p'); $p = lc($v); if ("*total*file*" iswm $p || "*search*result*" iswm $p || $v ismatch $r || "*too*many*re*" iswm $p || "*match*for*" iswm $p || "*copy*paste*channel*" iswm $p) { halt; } } } # [$mpxtModel removeRow:[$mpxtModel getRowCount] - 1]; on request { if (!$mpxWin) { return; } local('$r $f $sreq'); $sreq = getProperty('dcc.onsend'); $r = '(?i)^DCC SEND (.*) \d+ \d+ \d+$'; if ($parms ismatch $r) { $f = matched()[0]; if (mpxStrMatch($f) == 0) { return; } setProperty('dcc.onsend', 1); call("/dcc accept $nick"); mpxDCCStatUpdate($f, $nick); if ($mpx_DCCSTATS_TIMER is $null) { $mpx_DCCSTATS_TIMER = addTimer(&mpxDCCStats, 1000); } setProperty('dcc.onsend', $sreq); return; } } sub mpxStrMatch { local('$var'); foreach $var (@mpxSearchString) { if ($var iswm lc($1)) { return 1; } } return 0; } # mpxDCCStatUpdate(file, nick); sub mpxDCCStatUpdate { local('$max $file $user'); $max = [$mpxrTableModel getRowCount] - 1; while ($max >= 0) { $file = [$mpxrTableModel getValueAt: $max, 0]; $user = [$mpxrTableModel getValueAt: $max, 1]; if (mpxFileSendMatch($1, $file) && lc($user) eq lc($2)) { [$mpxrTableModel setValueAt: "ACKNOWLEDGING...", $max, 3]; } $max--; } } # [$mpxrTableModel getValueAt: 0, 0] file getDCCFileName(); # [$mpxrTableModel getValueAt: 0, 1] nick = getDCCNickname(getActiveConnections()[0]) # [$mpxrTableModel getValueAt: 0, 2] chan sub mpxFileSendMatch { local('$a $b'); $a = lc($1); $b = lc($2); if ($a eq $b) { return 1; } if (strrep($a, ' ','_') eq $b) { return 1; } if ($a eq strrep($b, ' ','_')) { return 1; } if (strrep($a, ' ','_') eq strrep($b, ' ','_')) { return 1; } return 0; } sub mpxDCCStats { if (!$mpxWin && $mpx_DCCSTATS_TIMER) { # stopTimer($mpx_DCCSTATS_TIMER); # $mpx_DCCSTATS_TIMER = $null; # return; } local('$num $_f $_n $d $f $n'); $num = [$mpxrTableModel getRowCount] - 1; while ($num >= 0) { $_f = [$mpxrTableModel getValueAt: $num, 0]; $_n = [$mpxrTableModel getValueAt: $num, 1]; foreach $d (getActiveConnections()) { $f = getDCCFileName($d); $n = getDCCNickname($d); if (mpxFileSendMatch($f, $_f) && lc($n) eq lc($_n)) { [$mpxrTableModel setValueAt: mpxDCCFormat($d), $num, 3]; } } $num--; } } sub mpxDCCFormat { # 10% COMPLETE - 10KB of 100KB @ 16.4KB/s (Est. Time Remaining: NN:NN) local('$rcvd $total $perc $rate $est $f_rcvd $f_total'); $rcvd = getBytesReceived($1); $total = getExpectedSize($1); $perc = (100 * $rcvd) / $total; $rate = formatBytes(getTransferRate($1)) . "/s"; $est = formatTime(getTimeRemaining($1)); $f_rcvd = formatBytes($rcvd); $f_total = formatBytes($total); if ($perc == 100) { return "RECEIVE COMPLETE @ " . timeStamp(); } return "$perc $+ % Complete - $f_rcvd of $f_total @ $rate \(Est. Time Remaining\: $est $+ \)"; } on receive_complete { if (!$mpxWin) { return; } local('$d $f $n $num $_f $_n'); $d = getDCCConnection($this); $f = getDCCFileName($d); $n = getDCCNickname($d); # $nick also works. $num = [$mpxrTableModel getRowCount] - 1; while ($num >= 0) { $_f = [$mpxrTableModel getValueAt: $num, 0]; $_n = [$mpxrTableModel getValueAt: $num, 1]; if (mpxFileSendMatch($f, $_f) && lc($n) eq lc($_n)) { [$mpxrTableModel setValueAt: "RECEIVE COMPLETE @ " . timeStamp(), $num, 3]; } $num--; } } on receive_failed { if (!$mpxWin) { return; } local('$d $f $n $num $_f $_n'); $d = getDCCConnection($this); $f = getDCCFileName($d); $n = getDCCNickname($d); $num = [$mpxrTableModel getRowCount] - 1; while ($num >= 0) { $_f = [$mpxrTableModel getValueAt: $num, 0]; $_n = [$mpxrTableModel getValueAt: $num, 1]; if (mpxFileSendMatch($f, $_f) && lc($n) eq lc($_n)) { [$mpxrTableModel setValueAt: "RECEIVE FAILED @ " . timeStamp(), $num, 3]; } $num--; } } # the end