1use strict; 2use Irssi::Irc; 3use Irssi 20020217; # Irssi 0.8.0 4use vars qw($VERSION %IRSSI); 5$VERSION = "1.52"; 6%IRSSI = ( 7 authors => "Matti 'qvr' Hiljanen", 8 contact => 'matti\@hiljanen.com', 9 contributors => 'stefan@pico.ruhr.de, dieck@gmx.de, peder@ifi.uio.no', 10 name => "dccstat", 11 description => "Shows verbose or short information of dcc send/gets on statusbar (speed, size, eta etc.)", 12 license => "GPL, Version 2", 13 url => "http://matin.maapallo.org/softa/irssi", 14 sbitems => "dccstat" 15); 16 17# Theme settings: 18# sb_dccstat = "{sb $0-}"; 19# $0 = sb_ds_short(_waiting)/sb_ds_normal(_waiting) 20# sb_ds_short = "$0%G:%n$1%Y@%n$2kB/s%G:%n$4%G:%n$3"; 21# $0 = G/S 22# $1 = filename 23# $2 = transfer speed 24# $3 = percent 25# $4 = progressbar 26# sb_ds_short_waiting = "$0%G:%n$1 $2 $3 waiting"; 27# $0 = G/S 28# $1 = filename 29# $2 = to/from 30# $3 = nick 31# sb_ds_normal = "$0 $1: '$2' $3 of $4 [$8] $9 ($5) $6kB/s ETA: $7"; 32# $0 = GET/SEND 33# $1 = nick 34# $2 = filename 35# $3 = transferred amount 36# $4 = full filesize 37# $5 = percent 38# $6 = speed 39# $7 = ETA 40# $8 = progressbar 41# $9 = rotator thingy :) 42# sb_ds_normal_waiting = "$0 $1: '$2' $3 $4 $5 waiting"; 43# $0 = GET/SEND 44# $1 = nick 45# $2 = filename 46# $3 = full filesize 47# $4 = to/from 48# $5 = nick 49# sb_ds_separator = ", "; 50# 51# TODO: 52# new ideas more than welcome :) 53# 54# FAQ: 55# Q: my input line gets cleared every time dcc send/get starts or ends, 56# why's that?! 57# A: it's a bug in irssi which is already fixed in cvs (2002-03-24 Sunday 20:06) 58# so the solution: upgrade to cvs or live with it and wait until the next stable release 59# 60 61 62use Irssi::TextUI; 63use strict; 64 65my $dccstat_refresh=5; 66my ($refresh_tag, $old_refresh, $new_refresh, $displayed_since); 67my $visible = -1; 68my $displaying = 0; 69my @rot_bar = ('|', '/', '-', '\\\\\\\\'); 70my $rot_bar_n = 0; 71my %dccstat; 72 73sub cmd_print_help { 74 Irssi::print( 75 "%_Dccstat.pl Help:%_\n\n". 76 "Statusbar called dccstat should have appeared when you loaded this script,\n". 77 "now you need to add the dccstat item into that statusbar:\n". 78 " /statusbar dccstat add dccstat\n". 79 " /save\n\n". 80 " The default verbose mode will produce output like this: \n". 81 " [GET nick: 'foobar.avi' 5500kB of 11MB (50%) 99kB/s ETA: 00:03:00]\n". 82 " and the short mode looks like this:\n". 83 " [G:foobar.avi\@99kB/s:(50%)]\n\n". 84 " %_/SETs:%_\n". 85 " /set dccstat_refresh <secs> (default: 5)\n". 86 " /set dccstat_short_mode <ON/OFF> (default: OFF)\n". 87 " shorter output and doesn't show DCCs: None when there are no GET/SENDs\n". 88 " /set dccstat_hide_sbar_when_inactive <ON/OFF> (default: OFF)\n". 89 " hides the statusbar called dccstat when there are no GET/SENDs\n". 90 " /set dccstat_auto_short_limit (default: 2)\n". 91 " amount of dcc sends/gets we can have before we automagically switch to short mode\n". 92 " (when all the info wouldn't fit to statusbar). setting it to 0 will disable it.\n". 93 " /set dccstat_progbar_width (default: 10)\n". 94 " progressbar width in chars\n". 95 " /set dccstat_progbar_transferred (default: '%%g=%%n')\n". 96 " /set dccstat_progbar_position (default: '%%y>%%n')\n". 97 " /set dccstat_progbar_remaining (default: '%%r-%%n')\n". 98 " /set dccstat_cycle_through_transfers (default: OFF)\n". 99 " cycle trough the transfers (ON) or show all transfers at the same time (OFF, default)\n". 100 " /set dccstat_cycle_through_transfers_refresh <secs> (default: 5)\n". 101 " how long to show one transfer at a time\n". 102 " /set dccstat_filename_max_length (default: 17)\n". 103 " /set dccstat_filename_max_length_shortmode (default: 10)\n". 104 " how much to show of a filename in normal and short modes\n\n". 105 " /set dccstat_EXPERIMENTAL_fast_refresh (default: OFF)\n". 106 " use very experimental and super fast refreshing, will probably consume all cpu power,\n". 107 " depending on your connection speed. but hey, it's fun :)\n". 108 " /set dccstat_debug (default: OFF)\n". 109 " show debug messages\n". 110 " \n". 111 "\nSee also: STATUSBAR, DCC and theme help in the actual script" 112 ,MSGLEVEL_CRAP); 113} 114 115sub debug { 116 my ($text) = @_; 117 return unless Irssi::settings_get_bool('dccstat_debug'); 118 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 119 localtime(time); 120 $sec = sprintf("%02d", $sec); 121 $min = sprintf("%02d", $min); 122 $hour = sprintf("%02d", $hour); 123 Irssi::print("DEBUG(%_Dccstat.pl%_): ".$text." [$hour:$min:$sec]"); 124} 125 126sub startup_check { 127 debug("START-UP - DEBUG IS ON"); 128 my @dccs = Irssi::Irc::dccs(); 129 my $act; 130 foreach my $dcc (@dccs) { $act=$dcc if $dcc->{type} eq "SEND" || $dcc->{type} eq "GET"; }; 131 dcc_connected($act); 132} 133 134sub dcc_connected { 135 debug("entering dcc_connected"); 136 my ($dcc) = @_; 137 return unless $dcc->{type} eq "SEND" || $dcc->{type} eq "GET"; 138 debug("removing dcc connected -signal"); 139 Irssi::signal_remove('dcc connected', 'dcc_connected'); 140 my $refresh_msecs = (Irssi::settings_get_int('dccstat_refresh')*1000); 141 $refresh_msecs = ($dccstat_refresh*1000) if $refresh_msecs < 1000; 142 debug("adding normal timeout.."); 143 $refresh_tag=Irssi::timeout_add($refresh_msecs, 'refresh_dccstat', undef); 144 $old_refresh=Irssi::settings_get_int('dccstat_refresh'); 145 Irssi::signal_add_last('dcc destroyed', 'dcc_checklast'); 146 refresh_dccstat(); 147} 148 149sub dcc_setupcheck { 150 $new_refresh = Irssi::settings_get_int('dccstat_refresh'); 151 if ($new_refresh != $old_refresh) { 152 debug("setting a new refresh timeout"); 153 $new_refresh = ($new_refresh*1000); 154 Irssi::timeout_remove($refresh_tag); 155 $new_refresh = ($dccstat_refresh*1000) if $new_refresh < 1000; 156 $refresh_tag=Irssi::timeout_add($new_refresh, 'refresh_dccstat', undef); 157 $old_refresh=Irssi::settings_get_int('dccstat_refresh'); 158 } 159 refresh_dccstat(); 160} 161 162sub dcc_checklast { 163 my @dccs = Irssi::Irc::dccs(); 164 my $count = dcc_getcount(); 165 debug("check for last, count is '$count'"); 166 return unless $count == 0; 167 debug("was last, removing timeout '$refresh_tag'"); 168 Irssi::timeout_remove($refresh_tag); 169 Irssi::signal_remove('dcc destroyed', 'dcc_checklast'); 170 Irssi::signal_add('dcc connected', 'dcc_connected'); 171 refresh_dccstat(); 172} 173 174# this function calculates the average speed of the last 10 seconds. 175# i think that's better than irssis default way of calculating the 176# average speed from the whole transfer 177sub dcc_calcSpeed { 178 my @dccs = Irssi::Irc::dccs(); 179 foreach my $dcc (@dccs) { 180 next unless $dcc->{type} eq "SEND" || $dcc->{type} eq "GET"; 181 my $id = "$dcc->{created}" . "$dcc->{addr}" . "$dcc->{port}"; 182 if (defined($dccstat{$id}{'speed'})) { 183 my $old = $dccstat{$id}{'position'}; 184 my $current = $dcc->{transfd}; 185 my $speed = (($current-$old)/10); 186 unless ($dccstat{$id}{'speed'} == "-1" && ($current-$old) == 0) { 187 $dccstat{$id}{'speed'} = $speed; 188 } 189 $dccstat{$id}{'position'} = $current; 190 } else { 191 # new dcc 192 my $id = "$dcc->{created}" . "$dcc->{addr}" . "$dcc->{port}"; 193 debug("creating dcc hash '$id'"); 194 $dccstat{$id}{'speed'} = "-1"; 195 $dccstat{$id}{'position'} = "0"; 196 } 197 } 198 199 # let's remove old hashes 200 foreach my $hash (keys %dccstat) { 201 my $keep = 0; 202 foreach my $dcc (@dccs) { 203 my $id = "$dcc->{created}" . "$dcc->{addr}" . "$dcc->{port}"; 204 $keep = 1 if ($hash == $id); 205 } 206 if ($keep) { 207 debug("dcc '$hash' is still active, it's speed is '" . $dccstat{$hash}{'speed'} . "'"); 208 } else { 209 debug("deleting dcc '$hash'"); 210 delete $dccstat{$hash}; 211 } 212 } 213} 214 215### this function originally implemented by dieck@gmx.de 216sub dcc_calculateETA { 217 my $dcc = $_[0]; 218 my ($dccspeed, $dccleft, $going, $dccsecs, $dcctime); 219 220 # calculate current speed 221 $going=(time-$dcc->{starttime}); 222 $going=1 if $going==0; 223 my $id = "$dcc->{created}" . "$dcc->{addr}" . "$dcc->{port}"; 224 if (defined($dccstat{$id})) { 225 $dccspeed=$dccstat{$id}{'speed'}; 226 } else { 227 $dccspeed = -1; 228 } 229 ## speed in bytes/sec 230 if ($dccspeed > 0) { 231 232 # calculate left transfer size 233 $dccleft = ($dcc->{size}-$dcc->{transfd}); 234 ## size left in byte 235 236 $dccspeed=1 if $dccspeed==0; 237 $dccsecs = $dccleft / $dccspeed; 238 239 $dcctime = sprintf("%02d:%02d:%02d", int($dccsecs/60/60), int($dccsecs/60%60), int($dccsecs%60)); 240 } elsif ($dccspeed == "0") { 241 $dcctime = "stalled"; 242 } elsif ($dccspeed == "-1") { 243 $dcctime = "???"; 244 } else { 245 # panic! 246 $dcctime = "error!"; 247 } 248 return $dcctime; 249} 250 251### this function originally implemented by stefan_tomanek@web.de 252sub dcc_progbar { 253 my ($dcc) = @_; 254 my ($filebar, $nobar); 255 my $barwidth = Irssi::settings_get_int('dccstat_progbar_width'); 256 my $char1 = Irssi::settings_get_str('dccstat_progbar_transferred'); 257 my $char2 = Irssi::settings_get_str('dccstat_progbar_position'); 258 my $char3 = Irssi::settings_get_str('dccstat_progbar_remaining'); 259 if ($dcc->{size} > 0) { 260 my $width_per_size = ($barwidth) / $dcc->{size}; 261 my $transf_chars = sprintf("%.0f",($width_per_size * $dcc->{transfd})); 262 $filebar = $char1 x $transf_chars; 263 $nobar = $char3 x ($barwidth - $transf_chars - 1); 264 return "${filebar}${char2}${nobar}"; 265 } else { 266 return $barwidth x $char3; 267 } 268} 269 270sub dcc_calculateSIZE { 271 my $fsize = $_[0]; 272 my ($size, $unit, $div); 273 274 if ($fsize >= 1024*1024*1024) { $size = $fsize/1024/1024/1024; $unit = "GB"; $div = 2; } 275 elsif ($fsize >= 1024*1024) { $size = $fsize/1024/1024; $unit = "MB"; $div = 2; } 276 elsif ($fsize >= 1024) { $size = $fsize/1024; $unit = "kB"; $div = 0; } 277 else { $size = $fsize; $unit = "B"; $div = 0; } 278 $size = sprintf("%.${div}f", $size); 279 return "${size}${unit}"; 280} 281 282sub dcc_getcount { 283 my @dccs = Irssi::Irc::dccs(); 284 my $count = 0; 285 foreach my $dcc (@dccs) { $count++ if $dcc->{type} eq "GET" || $dcc->{type} eq "SEND"; } 286 return $count; 287} 288 289sub dccstat { 290 #debug("going into main function"); 291 my ($item, $get_size_only) = @_; 292 my @dccs=Irssi::Irc::dccs(); 293 my (@results, $results); 294 my $mode = Irssi::settings_get_bool('dccstat_short_mode'); 295 my $exp_flags = Irssi::EXPAND_FLAG_IGNORE_EMPTY | Irssi::EXPAND_FLAG_IGNORE_REPLACES; 296 my $theme = Irssi::current_theme(); 297 my $format = $theme->format_expand("{sb_dccstat}"); 298 my $count = dcc_getcount(); 299 if ($count>0) { 300 my $sendcount=0; 301 my $getcount=0; 302 my ( 303 $dccpercent, $dccspeed, $dcctype, $going, 304 $dccnick, $dccfile, $FooOfBar, $str, 305 $fsize, $transize, $dcceta, $from, 306 $to, $direction, $prep, $autolimit, 307 $separator, $dccprogbar, $dccrotbar 308 ); 309 foreach my $dcc (@dccs) { 310 next unless $dcc->{type} eq "SEND" || $dcc->{type} eq "GET"; 311 # if count is above the autolimit, we'll force the mode to short 312 # but not if we're cycling through transfers. 313 if (not Irssi::settings_get_bool('dccstat_cycle_through_transfers')) { 314 $autolimit=Irssi::settings_get_int('dccstat_auto_short_limit'); 315 $mode=1 if $count > $autolimit && $autolimit > 0; 316 } 317 318 $sendcount++ if $dcc->{type} eq "SEND"; 319 $getcount++ if $dcc->{type} eq "GET"; 320 321 $dccpercent = ($dcc->{size} == 0) ? "(0%)" : sprintf("%.1f", $dcc->{transfd}/$dcc->{size}*100)."%%"; 322 323 $going = (time-$dcc->{starttime}); 324 $going = 1 if $going==0; 325 326 my $id = "$dcc->{created}" . "$dcc->{addr}" . "$dcc->{port}"; 327 if (defined($dccstat{$id})) { 328 $dccspeed = $dccstat{$id}{'speed'}; 329 } else { 330 $dccspeed = -1; 331 } 332 if ($dccspeed >= 0) { 333 $dccspeed = sprintf("%.2f", ($dccspeed/1024)); 334 } else { 335 $dccspeed = sprintf("%.2f", ($dcc->{transfd}-$dcc->{skipped})/$going/1024); 336 } 337 338 $dcctype = $dcc->{type}; 339 340 $dccnick = $dcc->{nick}; 341 $dccnick =~ s/\\/\\\\/g; 342 $dccfile = $dcc->{arg}; 343 $dccfile =~ s/ /\240/g; 344 $dccfile =~ s/\\/\\\\/g; 345 346 # if filename is longer than 17 chars, we'll show only the first 15 chars 347 # and in short mode we'll show only 8 chars 348 # (lengths are now configurable, but the idea is the same) 349 my $max_normal = Irssi::settings_get_int('dccstat_filename_max_length'); 350 my $max_short = Irssi::settings_get_int('dccstat_filename_max_length_shortmode'); 351 if (!$mode) { 352 $dccfile=substr($dccfile, 0, $max_normal-2).".." if (length($dccfile) > $max_normal); 353 } else { 354 $dccfile=substr($dccfile, 0, $max_short-2).".." if (length($dccfile) > $max_short); 355 } 356 357 $fsize = dcc_calculateSIZE($dcc->{size}); 358 $transize = dcc_calculateSIZE($dcc->{transfd}); 359 $dccprogbar = dcc_progbar($dcc); 360 $dccprogbar =~ s/ /\240/g; 361 $dcceta = dcc_calculateETA($dcc); 362 363 if ($dcctype eq "GET") { $direction = "G"; $prep = "from"; } 364 if ($dcctype eq "SEND") { $direction = "S"; $prep = "to"; } 365 366 $dccrotbar = $rot_bar[$rot_bar_n]; 367 368 # short mode? 369 if ($mode) { 370 # theme? 371 if ($format) { 372 if ($dcc->{starttime} > 0) { 373 $str = $theme->format_expand("{sb_ds_short $direction $dccfile $dccspeed $dccpercent $dccprogbar $dccrotbar}", $exp_flags); 374 } else { 375 $str = $theme->format_expand("{sb_ds_short_waiting $direction $dccfile $prep $dccnick}", $exp_flags); 376 } 377 } else { 378 $str = "$direction%G:%n$dccfile"; 379 $str .= ($dcc->{starttime} > 0) ? "%G@%n${dccspeed}kB/s%G:%n$dccprogbar%G:%n$dccrotbar%G:%n$dccpercent" : " $prep $dccnick waiting"; 380 } 381 } else { 382 if ($format) { 383 if ($dcc->{starttime} > 0) { 384 $str = $theme->format_expand("{sb_ds_normal $dcctype $dccnick $dccfile $transize $fsize $dccpercent $dccspeed $dcceta $dccprogbar $dccrotbar}", $exp_flags); 385 } else { 386 $str = $theme->format_expand("{sb_ds_normal_waiting $dcctype $dccnick $dccfile $fsize $prep $dccnick}", $exp_flags); 387 } 388 } else { 389 $str = "$dcctype $dccnick: '$dccfile'"; 390 $str .= ($dcc->{starttime} > 0) ? " $transize of $fsize [$dccprogbar] $dccrotbar ($dccpercent) ${dccspeed}kB/s ETA: $dcceta" : " $fsize $prep $dccnick waiting"; 391 } 392 } 393 push @results,$str; 394 } 395 if (not Irssi::settings_get_bool('dccstat_cycle_through_transfers')) { 396 $separator = ($theme->format_expand("{sb_ds_separator}")) ? $theme->format_expand("{sb_ds_separator}") : ", "; 397 $results = join("$separator", @results); 398 } else { 399 if (scalar(@results)-1 < $displaying) { $displaying = 0 }; 400 $results = @results[$displaying]; 401 if (not $get_size_only) { 402 if ((time-$displayed_since) >= (Irssi::settings_get_int('dccstat_cycle_through_transfers_refresh'))) { 403 debug("refreshing cycle display"); 404 $displaying++; 405 $displayed_since = time; 406 } 407 } 408 } 409 } else { 410 $results="%_DCCs:%_ None" if !$mode; 411 } 412 if ($format) { 413 if ($count > 0) { 414 $results = "{sb_dccstat $results}" 415 } else { 416 $results = "{sb_dccstat $results}" unless $mode; 417 } 418 } else { 419 if ($count > 0) { 420 $results = "{sb $results}"; 421 } else { 422 $results = "{sb $results}" unless $mode; 423 } 424 } 425 $item->default_handler($get_size_only, "$results", undef, 1); 426} 427 428sub refresh_dccstat { 429 #debug("refreshing item"); 430 my $hide = Irssi::settings_get_bool('dccstat_hide_sbar_when_inactive'); 431 my $count = dcc_getcount(); 432 433 if ($hide && $count == 0) { 434 if ($visible == -1 || $visible == 1) { 435 Irssi::command("statusbar dccstat disable"); 436 debug("disabling statusbar"); 437 $visible = 0; 438 } 439 return; 440 } 441 if ($visible == 0 || $visible == -1) { 442 Irssi::command("statusbar dccstat enable"); 443 debug("enabling statusbar"); 444 $visible = 1; 445 } 446 Irssi::statusbar_items_redraw('dccstat'); 447 $rot_bar_n++; 448 $rot_bar_n %= @rot_bar; 449 450} 451 452my $fref = 0; 453sub dcc_fast_refresh { 454 if (Irssi::settings_get_bool('dccstat_EXPERIMENTAL_fast_refresh')) 455 { 456 refresh_dccstat(); 457 $fref++; 458 debug("transfer updated! ($fref)"); 459 } 460} 461 462Irssi::settings_add_int($IRSSI{'name'}, "dccstat_refresh", $dccstat_refresh); 463Irssi::settings_add_bool($IRSSI{'name'}, 'dccstat_short_mode', 0); 464Irssi::settings_add_bool($IRSSI{'name'}, 'dccstat_hide_sbar_when_inactive', 0); 465Irssi::settings_add_int($IRSSI{'name'}, 'dccstat_auto_short_limit', 2); 466Irssi::settings_add_int($IRSSI{'name'}, 'dccstat_progbar_width', 10); 467Irssi::settings_add_str($IRSSI{'name'}, 'dccstat_progbar_transferred', '%g=%n'); 468Irssi::settings_add_str($IRSSI{'name'}, 'dccstat_progbar_position', '%y>%n'); 469Irssi::settings_add_str($IRSSI{'name'}, 'dccstat_progbar_remaining', '%r-%n'); 470Irssi::settings_add_bool($IRSSI{'name'}, 'dccstat_cycle_through_transfers', 0); 471Irssi::settings_add_int($IRSSI{'name'}, 'dccstat_cycle_through_transfers_refresh', 10); 472Irssi::settings_add_bool($IRSSI{'name'}, 'dccstat_EXPERIMENTAL_fast_refresh', 0); 473Irssi::settings_add_bool($IRSSI{'name'}, 'dccstat_debug', 0); 474Irssi::settings_add_int($IRSSI{'name'}, 'dccstat_filename_max_length', 17); 475Irssi::settings_add_int($IRSSI{'name'}, 'dccstat_filename_max_length_shortmode', 10); 476 477Irssi::command_bind('dccstat', 'cmd_print_help'); 478 479Irssi::statusbar_item_register('dccstat', undef, 'dccstat'); 480Irssi::timeout_add('10000', 'dcc_calcSpeed', undef); 481Irssi::signal_add('dcc connected', 'dcc_connected'); 482Irssi::signal_add( 483 { 484 'setup changed' => \&dcc_setupcheck, 485 'dcc request' => \&refresh_dccstat, 486 'dcc created' => \&refresh_dccstat, 487 'dcc destroyed' => \&refresh_dccstat, 488 'dcc transfer update' => \&dcc_fast_refresh, 489 } 490 ); 491 492# Startup 493startup_check(); 494refresh_dccstat(); 495 496# lets save some global variables 497$old_refresh = Irssi::settings_get_int('dccstat_refresh'); 498 499Irssi::print("Dccstat.pl loaded - /dccstat for help"); 500 501# EOF 502