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