diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/fserve.pl | 3578 |
1 files changed, 3578 insertions, 0 deletions
diff --git a/scripts/fserve.pl b/scripts/fserve.pl new file mode 100644 index 0000000..0fdc350 --- /dev/null +++ b/scripts/fserve.pl @@ -0,0 +1,3578 @@ +#!/usr/bin/perl -w +############################################################################# +# +# FServe - file server for Irssi using DCC +# +# Copyright (C) 2001 Martin Persson +# Copyright (C) 2003 Andriy Gritsenko +# Copyright (C) 2002-2004 Piotr Krukowiecki +# +# +# If you have any comments, bug reports or anything else +# please contact me at piotr at pingu.ii.uj.edu.pl +# +# "Official" home page is at http://pingu.ii.uj.edu.pl/~piotr/irssi +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# +# Changelog +# ==================================================================== +# +# TODO: +# - when sending e.g. 3/2 files (e.g. because of min_upload), fserve +# ad should say it's 3/2 sends, not 2/2 as it is now +# - BUG: doesn't work if root_dir contains '+' ? +# - Improve distro: /fs distro clear, etc +# - possibility to, in case of failed send, not to resend file at once +# but to requeue it in slot X +# - More control in sends/queues (e.g. changing resends left, etc) +# - /fs show_current_sends_to_channel +# - restricted @find +# - user priorities: new priority_user option in queue_priority + +# /fs priouser nick +# - @find should search thorough dirs as well. +# - incorporate flood protection +# ? make sure all server tags and user nicks are first lc()'ed +# ? don't use send_user_msg, it's redundant +# ? don't use message levels, but set window number +# instead (might be better) +# - Add '/fs queue all' or '/fs queue *' etc. +# +# 2.0.0 (2004.05.09) +# * released rc4 without changes. Still a lot to do, but it's quite stable. +# +# 2.0.0rc4 (2004.01.27) +# * fixed "() queued (0 B)" queued files +# +# 2.0.0rc3 (2003.06.19) +# * fserve.pl works with old (before 0.8.6) irssi +# * bugfix: min_upload was not working +# * more documentation +# +# 2.0.0rc2 (2003.06.09) +# * fixed 'send speed < 0' bug +# * some queue-oriented fixes +# * fixed '/fs delt' to update remaining sends and queues +# * added '/fs queue *' to display all queues. +# +# 2.0.0rc1 (2003.06.01) Happy Child's Day :) +# * Changed format of config file, it won't work with old (1.2.4 and +# older file). If you're upgrading from 1.3.x and newer, just add +# "[ConfigFileVersion 1.0]" (without '"') at the beginning of the +# file. +# This should be the last user-visible change of config/queue files. +# * More documentation in /fs help +# * Reseting upload_counter after having sent file +# * renamed ignore_chat to ctcp_only +# * renamed short_notice to custom_notice, added custom_notice_fields +# * @find responses more Sysreset-like +# +# Important changes between 1.2.4 and 2.0.0rc1 +# (for detailed version look at fserve-1.4.0pre6) +# Many thanks to Andriy Gritsenko for his work on the fserve. +# * multiple server support +# * multiple queue support (patch from A.G) +# * good documentation: '/fs help' (although it's still not complete) +# * changed format of queue file, saved sends and queues won't be back. +# * many bugfixes, small fixes, changes in server logic etc. +# * big patch from A.G, too much changes to list here. +# +# +# 1.2.4 +# * bug workaround: removing ghost users (not tested... i don't have +# such problems...) +# * Removed window_close_on_quit - it was causing irssi to crash +# * Patch from Daniel Seifert (dseifert at gmx dot de): +# - added dont_notify option (to define channels where no notifies +# should be sent to) +# - english corrections +# +# 1.2.3 +# * Added: +# - offline_message which is displayed when someone wants to access +# disabled fserve +# - fserve responds to !olist if (restricted_level > 0) and to +# !vlist if (restricted_level == 1) +# - fserve responds to "!list <my irc nick>" +# * bug (?) workaround: sometimes fserve thinks it's still sending +# the file when it's not. Now it's checking for such ghost sends +# and removes them from sends list +# * bugfix: can send files containing "'" now +# +# 1.2.2 +# * works with irssi 0.8.6 now, but doesn't work with irssi 0.8.5 and +# former (incompatybile change in irssi 0.8.6 :( ) +# +# 1.2.1 +# * bugfix: @find didn't reported any files if there was only one match +# +# 1.2.0 +# * IMPORTANT CHANGE: there is no longer 'ops_priority' setting. You must +# use 'queue_priority' instead (irssi will switch to it automatically +# when loading old config). queue_priority is a list of space separated +# priorities: "normal", "voice", "halfop", "op" and "others". Queue +# is sorted according to the order in which they appear in queue_priority. +# For example, if you set it to 'voice others normal' then first in queue +# will be voiced people, then people with priority not mentioned in +# queue_priority (in this case halfops and ops), then normal people. +# If 'others' doesn't exists in queue_priority it's assumed to be at +# the end +# * Added: +# - '/fs sortqueue' to sort queue according to queue_prority +# - count_send_as_queue setting. If set to 1 user sends take +# place in queue. For example, if it's set and user_slots == 1, +# user can have only one send, or only one queued file. +# - distro mode (/fs set distro, distro_file). When distro = 1 +# fileserver counts how many times each file was sent, and first +# sends files with lowest send count. +# In fact, distro setting isn't simply 0/1. It's a PROBABILITY of +# using distro mode for the send. The values should be from range +# [0,1], where 0 means don't use distro mode at all, and 1 means +# allways use distro mode. For example when it's set to 0.7 it'll +# use distro mode in 7 cases of 10 (more or less). +# - '/fs distro stats' displays send count for files +# * bugfix: +# - send speed was wrongly calculated. +# - fserve could sometimes use wrong network +# - exit, bye shoult works now. Patch from Jan Rekorajski +# (baggins at sith.mimuw.edu.pl). Chat windows are closed unless +# close_window_on_quit is set to 0 +# * in conffile, queuefile and log_name you can use $IRSSI as part of the +# path. It will be changed to Irssis home directory. +# * hopefully better support for fserve explorers etc (changed 'dir' output) +# * people who use different command char then '/' in /command shouldn't +# have problems now +# * some other fixes/changes +# +# 1.1.3 +# * added: +# - +v/+%/+o only fserve. setting restricted_level to 3 means only ops +# can access, to 2 only ops and halfops, to 1 only ops, halfops and +# voiced users can access. if it's 0 everybody can access. +# +# 1.1.2 +# * added: +# - !request support (/fs set request) +# +# 1.1.1 +# * bugfix: +# - works with files containing more than one space in row +# (e.g. 'blah blah') +# * added: +# - /fs set autosave_on_close - when set to 1 sends and queues +# will be saved on /fs off +# +# 1.1.0 +# * bugfix: +# - Enabling debug (/fs set debug 1) works now +# * New: +# - /fs set content - adds "On Fserve:(content)" to notice. +# - /fs set motdfile - gets MOTD from file +# - /fs set recache_interval - does /fs recache every recache_interval +# seconds +# - /ctcp ... NoResend +# +# 1.0.0 +# ----- +# * added: +# - sending small files without waiting in queues +# (/fs set instant_send). Patch from Jan Rekorajski +# (baggins at sith.mimuw.edu.pl) +# - @find support (/fs set find, /fs set find_results). Patch from +# Jan Rekorajski (baggins at sith.mimuw.edu.pl +# - queuefile and $conffile in $fs_prefs{} +# - /fs notify #channel1 #channel2 #etc +# - current upstream is displayed in server notice +# - resends ($max_resends) and better min_cps handling ($speedp). New +# log position (dcc_soft_fail) if resend is possibile +# - MOTD - '/fs set motd blah blah' +# * bugfixes +# - fserver should respond to all !list's (comparing # names not cases s.) +# - fixed '/fs insert file' +# - displays notice with correct colors even if Note: contains braces +# - queued position reported after queueing file by +o/+v with +# ops_priority on +# * moved most usefull variables to %fs_prefs (/fs set ...) +# * priority users are moved to the beginnign of the queue +# * 'Autosaving...' is not printed anymore unless in debug mode +# * Previously if ops_priority was on and nick was +o/+v the file was added +# even if there was no free queue slot. Now it's not added, unless +# ops_priority > 2. +# * if irc server disconnects, fserve will change to 'frozen' state and will +# wait for reconnection, then will wait next 150s to join channels etc. +# If send will fail in that time then it will be moved to queue. +# If you want to manually connect to new irc server, do /fs off, /fs on +# +# -- +# Changes above by Cvbge (piotr at pingu.ii.uj.edu.pl) +# -- +# +# 0.6.0 +# ----- +# +# * Merged patch from Ethan Fischer (allanon@crystaltokyo.com) +# - added ignore_chat option that, when turned on, ignores the +# trigger if said in the channel; it also changes the trigger +# advertisement to "/ctcp nick !trigger" +# - added ops_priority option that, when set to 1, force-adds +# requests from to the top of the download queue regardless of +# queue size; when set to 2, it does the same thing for voices +# - added log_name option to specify the name of a logfile which +# will be used to store transfer logs; the log contains the time +# a dcc transfer finishes, whether it finished or failed, filename, +# nick, bytes sent, start time, and end time +# - added a kludge to kill dcc chats after an "exit" in sig_timeout() +# - added a -clear option to the set command (eg, /fs set -clear +# log_name) which sets the variable to an empty string +# +# * Merged patch from Brian (btherl@optushome.com.au) +# - Avoid division by zero when dcc send takes 0 time to complete +# - new user command "read" - allows reading of small (<30k) files, +# such as checksum files +# - set line delimeter before load_config() +# - formatting of function headers +# +# thanks for the patches guys :) +# +# * the bytecounter now also counts the number of bytes sent +# for failed transfers as well as successful transfers +# (with respects to resumed files) +# * some bugfixes I don't remember ;) +# +############################################################################# + +# Best viewed with TAB size = 4 ! + +use strict; +no strict 'refs'; + +use Irssi; +use Irssi::Irc; + +use vars qw($VERSION %IRSSI); + +$VERSION = "2.0.0"; +my $conffile = '$IRSSI/fserve.conf'; + +%IRSSI = ( + authors => 'Piotr Krukowiecki & others', + contact => 'piotr at pingu.ii.uj.edu.pl', + name => 'FServe', + description => 'File server for irssi', + license => 'GPL v2', + url => 'http://pingu.ii.uj.edu.pl/~piotr/irssi' +); + + +my @welcome_msg = ( + "FServe $VERSION for Irssi", + "-", + "Commands: ls dir cd get read dequeue clr_queue queue sends", + " help who stats quit", +); + +my @help_msg = ( + "-=[ Available commands ]=-", + " ls / dir - list files in current directory", + " cd <dir> - changes current directory to <dir>", + " (note: <dir> is case sensitive!)", + " get <file> - inserts <file> into the queue", + " read <file> - displays contents of <file>", + " dequeue <nr> - removes file in slot <nr>", + " clr_queue[s] - removes your queued files", + " queue[s] - lists the queue", + " sends - lists active sends", + " who - lists users online", + " stats - shows some statistice", + " quit - closes the connection", +); + +my @srv_help_msg = ( + "command - [params] description\003\n", + "on - [0] enables fileserver", + "off - [0] disables fileserver", + "save - [0] save config file", + "load - [0] load config file", + "saveq - [0] saves sends/queues", + "loadq - [0] loads the queues", + "set - [0/2] sets variables", + "addq - [0] adds new queue", + "delq - [1] deletes queue", + "selq - [1] sets default queue for next 4 commands", + "setq - [0/2] sets queue variables", + "queue - [0-1] lists file queue", + "sortq - [0-1] sorts queue", + "move - [2-3] moves queue slots around", + "insert - [3] inserts a file in queue", + "clear - [1] removes queued files", + "sends - [0] lists active sends", + "who - [0] lists users online", + "stats - [0] shows server statistics", + "recache - [0] updates filecache\003\n", + "Usage: /fs <command> [<arguments>]", + "For parameter info type /fs <cmd>", + "Please read beginning of the fserve.pl (the changelog)", + "for more information", +); + +############################################################################### +# fileserver preferences (/fs set <var> <data>) +# default values, feel free to change them +############################################################################### +my %fs_prefs = ( + auto_save => 599, + autosave_on_close => 1, + clr_dir => "\00312", + clr_file => "\00315", + clr_hi => "\00312", + clr_txt => "\00315", + count_send_as_queue => 0, + debug => 0, + distro => 0, + distro_file => '$IRSSI/fserve.distro', + idle_time => 120, + ignores => "", + log_name => '$IRSSI/fserve.log', # FIXME should be renamed to logfile or similar + max_queues => 10, + max_sends => 2, + max_time => 600, + max_users => 5, + min_upload => 0, + motd => '', + motdfile => '', + offline_message => '', # is displayed when someone wants to enter disabled fserve + queuefile => '$IRSSI/fserve.queue', + recache_interval => 3607, +); + +my %fs_queue_defaults = ( + channels => '#CHANGE_ME', + content => '', + ctcp_only => 1, + custom_notice => 1, + custom_notice_fields=> "trigger sends queues min_cps note content", + dont_notify => "", + find => 3, + guaranted_queues => 0, + guaranted_sends => 0, + ignore_msg => 1, + ignores => "", + instant_send => 10240, + max_queues => 10, + max_resends => 3, + max_sends => 2, + min_cps => 9728, + motd => '', + nice => 0, + note => '', + notify_interval => 0, + notify_on_join => 0, + queue_priority => "", + request => "", + restricted_level => 0, + root_dir => '/path/to/files/CHANGE_ME', + servers => 'CHANGE_ME', + speed_warnings => 1, + trigger => '!trigger', + user_slots => 3, +); + +############################################################################### +# fileserver statistics +############################################################################### +my %fs_stats = ( + record_cps => 0, + rcps_nick => "", + sends_ok => 0, # sends succeeded + sends_fail => 0, # sends failed + transfd => 0, # total bytes transferred + login_count => 0, # total number of logins +); + +my @fs_queues = (); +my @fs_sends = (); +my %fs_users = (); +my %fs_distro = (); + +############################################################################### +# private variables +############################################################################### +my $fs_enabled = 0; # always start disabled +my $online_time = 0; # time since last script restart +my $timer_tag; +my $logfp; +my @kill_dcc; +my $upload_counter = 0; +my $last_upload = 0; +my $last_upload_check = 0; +my $motdfile_modified = 0; #when was motd file last modified +my @motd = (); +my $default_queue = 0; +my $next_queue = 0; +my $FD = "'"; # old irssi (<0.8.6) doesn't use "'" in /dcc send 'file' + +############################################################################### +# setup signal handlers +############################################################################### +Irssi::signal_add_first('event privmsg', 'sig_event_privmsg'); +Irssi::signal_add_first('event join', 'sig_event_join'); +Irssi::signal_add_first('default ctcp msg', 'sig_ctcp_msg'); +Irssi::signal_add_last('dcc chat message', 'sig_dcc_msg'); + +Irssi::signal_add_last('dcc connected', 'sig_dcc_connected'); +Irssi::signal_add('dcc destroyed', 'sig_dcc_destroyed'); + +Irssi::signal_add('nicklist changed', 'sig_nicklist_changed'); + +Irssi::command_bind('fs', 'sig_fs_command'); +print_msg("FServe version $VERSION"); +print_log("FServe starting up"); + +$_ = $conffile; +s/\$IRSSI/Irssi::get_irssi_dir()/e or s/~/$ENV{"HOME"}/; +if (-e) { + load_config(); +} else { + print_msg("If this is your first time using this fserve"); + print_msg("I advise you to read help (/fs help)"); +} +if (!@fs_queues) { + print_debug("Added inital trigger"); + push (@fs_queues, { %fs_queue_defaults }); + @{$fs_queues[$#fs_queues]->{queue}} = (); +} + +{ + my $ver = 'Very Old'; + eval { $ver = Irssi::version(); }; + if ($ver - 20021117 < 0) { + print_debug("Detected old irssi version: $ver") ; + $FD = ""; + } +} + +if ($fs_prefs{distro} and $fs_prefs{distro_file}) { + $_ = $fs_prefs{distro_file}; + s/\$IRSSI/Irssi::get_irssi_dir()/e or s/~/$ENV{"HOME"}/; + if (-e) { + load_distro($_) and print_msg("Distro file loaded"); + } +} + +############################################################################### +# prints debug messages in the (fserve_dbg) window +############################################################################### +sub print_debug +{ + if ($fs_prefs{debug}) { + Irssi::print("<DBG> @_", MSGLEVEL_CLIENTERROR); + } +} + +############################################################################### +# prints server message in current window +############################################################################### +sub print_msg +{ + Irssi::active_win()->print("$fs_prefs{clr_txt} @_"); +} + +sub print_what_we_did { + Irssi::print("@_", MSGLEVEL_CLIENTCRAP); +} + +sub max($$) { return @_[0]>@_[1]?@_[0]:@_[1]; } +sub min($$) { return @_[0]<@_[1]?@_[0]:@_[1]; } + +############################################################################### +############################################################################### +## +## Signal handler routines +## +############################################################################### +############################################################################### + +sub get_max_sends($) { + my $qn = @_[0]; + + my $qu_msends = $fs_queues[$qn]->{max_sends}; + my $gl_msends = $fs_prefs{max_sends}; + my $guaranted_sends = $fs_queues[$qn]->{guaranted_sends}; + + my $current_sends = $fs_queues[$qn]->{sends}; + my $free_sends = + max( $guaranted_sends - $current_sends, + min($gl_msends - @fs_sends, $qu_msends - $current_sends) ); + $free_sends = 0 if ($free_sends < 0); + my $max_sends = max( $guaranted_sends, min($qu_msends,$gl_msends) ); + + return ($current_sends, $free_sends, $max_sends); +} + +sub get_max_queues($) { + my $qn = @_[0]; + + my $qu_mqueues = $fs_queues[$qn]->{max_queues}; + my $gl_mqueues = $fs_prefs{max_queues}; + my $guaranted_queues = $fs_queues[$qn]->{guaranted_queues}; + # TODO: keep this somewhere? + my $gl_current_queues = 0; + foreach (0 .. $#fs_queues) { + $gl_current_queues += @{$fs_queues[$_]->{queue}}; + } + + my $current_queues = @{$fs_queues[$qn]->{queue}}; + my $free_queues = + max( $guaranted_queues - $current_queues, + min($gl_mqueues - $gl_current_queues, + $qu_mqueues - $current_queues) ); + $free_queues = 0 if ($free_queues < 0); + my $max_queues = max( $guaranted_queues, min($qu_mqueues, $gl_mqueues) ); + + return ($current_queues, $free_queues, $max_queues); +} + +############################################################################### +# updates some variables when DCC CHAT is established +############################################################################### +sub sig_dcc_connected +{ + my ($dcc) = @_; + my $tag = $dcc->{servertag}; + my $user_id = $dcc->{nick}."@".$tag; + print_debug("DCC connected: $dcc->{type} $user_id"); + + return if ($dcc->{type} ne "CHAT" || !defined $fs_users{$user_id}); + + print_debug("User $user_id connected!"); + $fs_users{$user_id}{status} = 0; + $fs_users{$user_id}{time} = 0; + $fs_stats{login_count}++; + + foreach (@welcome_msg) { + send_user_msg($tag, $dcc->{nick}, $_); + } + send_user_msg($tag, $dcc->{nick}, "-"); + + my $qn = $fs_users{$user_id}{queue}; + my ($curr_queues, $free_queues, $max_queues) = get_max_queues($qn); + my ($curr_sends, $free_sends, $max_sends) = get_max_sends($qn); + + send_user_msg($tag, $dcc->{nick}, "Current/Free/Max Sends: ". + "$curr_sends/$free_sends/$max_sends"); + send_user_msg($tag, $dcc->{nick}, "Current/Free/Max Queues: ". + "$curr_queues/$free_queues/$max_queues"); + send_user_msg($tag, $dcc->{nick}, "Your queue: ". + count_user_files($tag, $dcc->{nick}, $qn). + "/$fs_queues[$qn]->{user_slots}"); + + send_user_msg($tag, $dcc->{nick}, "Instant send: ". + size_to_str($fs_queues[$qn]{instant_send})) + if ($fs_queues[$qn]{instant_send} > 0); + + if ($fs_prefs{motdfile}) { + send_user_msg($tag, $dcc->{nick}, "-"); + my $f = $fs_prefs{motdfile}; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + if (! ((-f $f) and (-r $f))) { + print_msg("FServe: '$f' doesn't exists, isn't plain file or is not readable"); + } else { + my $lm = (stat($f))[9]; + if ($motdfile_modified < $lm) { + $motdfile_modified = $lm; + @motd = (); + open(FILE, "<", $f); + while(<FILE>) { + chomp; + s/\t/ /g; + push @motd, $_; + } + close(FILE, $f); + } + foreach (@motd) { + send_user_msg($tag, $dcc->{nick}, $_); + } + } + } + + if (length($fs_prefs{motd})) { + send_user_msg($tag, $dcc->{nick}, "-"); + send_user_msg($tag, $dcc->{nick}, "$fs_prefs{motd}"); + } + if (length($fs_queues[$qn]{motd})) { + send_user_msg($tag, $dcc->{nick}, "-"); + send_user_msg($tag, $dcc->{nick}, "$fs_queues[$qn]{motd}"); + } + send_user_msg($tag, $dcc->{nick}, "-"); + send_user_msg($tag, $dcc->{nick}, '[\]'); +} + +############################################################################### +# cleanups after DCC CHAT/SEND disconnects +############################################################################### +sub sig_dcc_destroyed +{ + my ($dcc) = @_; + my $nick = $dcc->{nick}; + my $server = $dcc->{server}; + my $server_tag = $dcc->{servertag}; + my $user_id = $nick.'@'.$server_tag; + + print_debug("DCC destroyed: $dcc->{type} $user_id '$dcc->{arg}'"); + + if ($dcc->{type} eq "CHAT" && defined $fs_users{$user_id}) { + delete $fs_users{$user_id}; + print_debug("Users left: ".keys %fs_users); + } elsif ($dcc->{type} eq "SEND") { + foreach my $sn (0 .. $#fs_sends) { + print_debug("check slot $sn: ". + "user=$fs_sends[$sn]->{nick}\@$fs_sends[$sn]->{server_tag}, ". + "file=$fs_sends[$sn]->{file}."); + if ($fs_sends[$sn]->{nick} eq $nick && + $fs_sends[$sn]->{server_tag} eq $server_tag && + $fs_sends[$sn]->{file} eq $dcc->{arg}) { + print_debug("found send in slot $sn"); + if ($dcc->{transfd} == $fs_sends[$sn]->{size}) { + print_log("dcc_finish $dcc->{arg} $user_id ". + "$dcc->{skipped} $dcc->{transfd} ". + "$dcc->{starttime} ".time()); + print_debug("file was finished"); + $fs_stats{sends_ok}++; + if ($fs_prefs{distro}) { + $fs_distro{$dcc->{arg}}{$dcc->{transfd}}++; + save_distro(); + } + + ## Update speed record (if new) + if (time() > $dcc->{starttime}) { + my $speed = ($dcc->{transfd}-$dcc->{skipped})/ + (time() - $dcc->{starttime}); + + if ($speed > $fs_stats{record_cps}) { + $fs_stats{record_cps} = $speed; + $fs_stats{rcps_nick} = $nick; + } + } + } else { + if ($fs_sends[$sn]->{transfd} == -1) { + # send was too slow + print_log("dcc_abort $dcc->{arg} $user_id ". + "$dcc->{skipped} $dcc->{transfd} ". + "$dcc->{starttime} ".time()); + } else { + $fs_sends[$sn]->{resends} += 1; + $fs_sends[$sn]->{warns} = 0; + $fs_sends[$sn]->{dontwarn} = 0; + delete $fs_sends[$sn]->{transfd}; + + if ($fs_sends[$sn]->{resends} <= + $fs_queues[$fs_sends[$sn]{queue}]{max_resends}) { + + # queue it for resending + # don't resend right now, you may be treated as flood + my $fsq = $fs_queues[$fs_sends[$sn]->{queue}]->{queue}; + # TODO should be parametrized (in which slot requeue) + my $resended_queue = 0; + foreach (0 .. $#{$fsq}) { + last if (!${$fsq}[$_]->{resends}); + $resended_queue++; + } + $resended_queue = 1 + if (!$resended_queue && @{$fsq}>0); + print_debug("requeued $dcc->{arg} for ". + "$user_id in slot $resended_queue, ". + "resend $fs_sends[$sn]->{resends}"); + splice(@{$fsq}, $resended_queue, 0, { %{$fs_sends[$sn]} }); + $server->command("^NOTICE ". + "$fs_sends[$sn]->{nick} ". + "$fs_prefs{clr_txt} Send failed on try ". + $fs_sends[$sn]->{resends}." of ". + ($fs_queues[$fs_sends[$sn]{queue}]{max_resends}+1). + ". Type /ctcp ". + "$$server{nick} NoReSend to cancel " + ."any further resends.") + if ($server && $server->{connected}); + print_what_we_did("NOTICE ". + "$fs_sends[$sn]->{nick} ". + "$fs_prefs{clr_txt} Send failed on try ". + $fs_sends[$sn]->{resends}." of ". + ($fs_queues[$fs_sends[$sn]{queue}]{max_resends}+1). + ". Type /ctcp ". + "$$server{nick} NoReSend to cancel " + ."any further resends.") + if ($server && $server->{connected}); + print_log("dcc_soft_fail $dcc->{arg} $user_id ". + "$dcc->{skipped} $dcc->{transfd} ". + "$dcc->{starttime} ".time()); + } else { + print_log("dcc_fail $dcc->{arg} $user_id ". + "$dcc->{skipped} $dcc->{transfd} ". + "$dcc->{starttime} ".time()); + } + } + $fs_stats{sends_fail}++; + } + + ## Update bytes transferred + $fs_stats{transfd} += ($dcc->{transfd} - $dcc->{skipped}); + splice(@fs_sends, $sn, 1); # FIXME : decrease number of sends? + print_debug("SEND closed to $user_id, file: ". + "$dcc->{arg}, bytes sent: ". + ($dcc->{transfd}-$dcc->{skipped}). + " (sent from slot $sn, ".@fs_sends." slots now)"); + return; + } + } + } +} + +############################################################################### +# handles dcc chat messages +############################################################################### +sub sig_dcc_msg +{ + my $dcc = shift (@_); + my $msg = @_[0]; + my $user_id = $dcc->{nick}.'@'.$dcc->{servertag}; + + # ignore messages from unconnected dcc chats + return unless ($fs_enabled && defined $fs_users{$user_id}); + + # reset idle time for user + $fs_users{$user_id}{status} = 0; + + my ($cmd, $args) = split(' ', $msg, 2); + $cmd = lc($cmd); + + if ($cmd eq "dir" || $cmd eq "ls") { + list_dir($user_id, "$args"); + } elsif ($cmd eq "cd") { + change_dir($user_id, "$args"); + } elsif ($cmd eq "cd..") { # darn windows users ;) + change_dir($user_id, '..'); + } elsif ($cmd eq "get") { + queue_file($user_id, "$args"); + } elsif ($cmd eq "dequeue") { + $args =~ s/^\D*(\d+)\D*$/$1/; # stupid leechers, we have to remove garbage + dequeue_file($user_id, $args); + } elsif ($cmd eq "clr_queue" || $cmd eq "clr_queues") { + clear_queue($user_id, 0, $fs_users{$user_id}{queue}); + } elsif ($cmd eq "queue" || $cmd eq "queues") { + display_queue($user_id, $fs_users{$user_id}{queue}); + } elsif ($cmd eq "sends") { + display_sends($user_id); + } elsif ($cmd eq "who") { + display_who($user_id); + } elsif ($cmd eq "stats") { + display_stats($user_id); + } elsif ($cmd eq "read") { + display_file($user_id, "$args"); + } elsif ($cmd eq "help") { + foreach (@help_msg) { + send_user_msg($dcc->{servertag}, $dcc->{nick}, $_); + } + } elsif ($cmd eq "exit" || $cmd eq "quit" || $cmd eq "bye") { + push(@kill_dcc, $user_id); + } +} + +############################################################################### +# server, nick, queue_number +############################################################################### +sub try_connecting_user ($$$) +{ + my ($server, $sender, $qn) = @_; + my $tag = $server->{tag}; + + if (defined($fs_users{$sender."@".$tag})) { + if (!$fs_users{$sender."@".$tag}{ignore} && + $fs_queues[$qn]->{ignore_msg}) { + $server->command("^NOTICE $sender $fs_prefs{clr_txt}". + "A DCC chat offer has already been sent to you!"); + print_what_we_did("NOTICE $sender $fs_prefs{clr_txt}". + "A DCC chat offer has already been sent to you!"); + } + + $fs_users{$sender."@".$tag}{ignore} = 1; + return 1; + } + + if (keys(%fs_users) < $fs_prefs{max_users}) { + if (!$fs_queues[$qn]->{restricted_level}) { + initiate_dcc_chat($server, $sender, $qn); + return 1; + } else { + foreach (split (' ', $fs_queues[$qn]->{channels})) { + my $ch = $server->channel_find($_); + next if !$ch; + my $n = $ch->nick_find($sender); + next if !$n; + if (($n->{op}) or + (($fs_queues[$qn]->{restricted_level} < 3) && $n->{halfop}) or + (($fs_queues[$qn]->{restricted_level} < 2) && $n->{voice})) { + initiate_dcc_chat($server, $sender, $qn); + return 1; + } + } + $server->command("^NOTICE $sender $fs_prefs{clr_txt}I'm sorry," + ." but this trigger is restricted. You need to be an". + (($fs_queues[$qn]->{restricted_level} == 3) ? " op" : + (($fs_queues[$qn]->{restricted_level} == 2) ? " op or halfop" : + " op, halfop or voiced")) . " to access this trigger"); + print_what_we_did("NOTICE $sender $fs_prefs{clr_txt}I'm sorry," + ." but this trigger is restricted. You need to be an". + (($fs_queues[$qn]->{restricted_level} == 3) ? " op" : + (($fs_queues[$qn]->{restricted_level} == 2) ? " op or halfop" : + " op, halfop or voiced")) . " to access this trigger"); + } + } else { + $server->command("^NOTICE $sender $fs_prefs{clr_txt}". + "Sorry, server is full (". + $fs_prefs{clr_hi}.$fs_prefs{max_users}. + $fs_prefs{clr_txt}.")!"); + print_what_we_did("NOTICE $sender $fs_prefs{clr_txt}". + "Sorry, server is full (". + $fs_prefs{clr_hi}.$fs_prefs{max_users}. + $fs_prefs{clr_txt}.")!"); + } + return 0; +} + + +############################################################################### +# handles ctcp messages +############################################################################### +sub sig_ctcp_msg +{ + my ($server, $args, $sender, $addr, $target) = @_; + $args = uc($args); + $args =~ s/\s*$//; # strip ending spaces + my $tag = $server->{tag}; + + return if ($fs_prefs{ignores} && + $server->masks_match($fs_prefs{ignores}, $sender, $addr)); + + if (!$fs_enabled) { + # find queue where the trigger is + foreach (0 .. $#fs_queues) { + next if ($args ne uc($fs_queues[$_]->{trigger})); + next if ($fs_queues[$_]{ignores} && + $server->masks_match($fs_queues[$_]{ignores}, $sender, $addr)); + + foreach my $s (split(' ', $fs_queues[$_]->{servers})) { + if (uc($s) eq uc($tag) && + user_in_channel($server, $sender, $fs_queues[$_])) { + + $server->command("^NOTICE $sender $fs_prefs{clr_txt}". + "Sorry, fserve is currently offline. $fs_prefs{offline_message}"); + print_what_we_did("NOTICE $sender $fs_prefs{clr_txt}". + "Sorry, fserve is currently offline. $fs_prefs{offline_message}"); + Irssi::signal_stop(); + return; + } + } # loop over servers + } # loop over queues + Irssi::signal_stop(); + return; + } + + print_debug("CTCP from $sender: '$args'"); + + if ($args eq "NORESEND") { + my $found = 0; + foreach (0 .. $#fs_sends) { + if ($fs_sends[$_]{nick} eq $sender && + $fs_sends[$_]{server} eq $tag) { + print_debug("$sender: Canceling resends of $fs_sends[$_]->{file}"); + $fs_sends[$_]->{resends} = $fs_queues[$fs_sends[$_]{queue}]{max_resends}; + $found++; + } + } + my $message = ($found? + "Resend: All resends ($found) for currently sending ". + "files have been canceled." : + "Resend: You currently have no sending files set ". + "to resend."); + $server->command("^MSG $sender $message"); + print_what_we_did("MSG $sender $message"); + Irssi::signal_stop(); + return; + } # end NORESEND + + + foreach my $qn (0 .. $#fs_queues) { + next if ($args ne uc($fs_queues[$qn]->{trigger})); + print_debug("Got trigger in queue $qn"); + next if ($fs_queues[$qn]{ignores} && + $server->masks_match($fs_queues[$qn]{ignores}, $sender, $addr)); + print_debug("Not ignoring user"); + + print_debug("Servers are $fs_queues[$qn]->{servers}"); + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + print_debug("Checking server $s against $tag"); + next if (uc($tag) ne uc($s) || + !user_in_channel($server, $sender, $fs_queues[$qn])); + print_debug("Good tag and user in chan"); + + if (try_connecting_user($server, $sender, $qn)) { + Irssi::signal_stop(); + return; + } + } + } + Irssi::signal_stop(); + return; +} + +############################################################################### +# notifies joining users +############################################################################### +sub sig_event_join +{ + my ($server, $data, $sender, $addr) = @_; + my ($target) = ($data =~ /:(.*)/); + + return if (!$fs_enabled); + + foreach my $qn (0 .. $#fs_queues) { + next if (!$fs_queues[$qn]->{notify_on_join}); + next if ($fs_queues[$qn]{ignores} && + $server->masks_match($fs_queues[$qn]{ignores}, $sender, $addr)); + + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + next if (uc($s) ne uc($server->{tag})); + foreach my $channel (split(' ', $fs_queues[$qn]->{channels})) { + next if (uc($channel) ne uc($target)); + show_notice($server, $sender, $qn); + } # loop over channels + } # loop over servers + + } # loop over queues + +} + +############################################################################### +# handles channel and private messages +############################################################################### +sub sig_event_privmsg +{ + my ($server, $data, $sender, $addr) = @_; + my ($target, $text) = split(/ :/, $data, 2); + + return if (!$fs_enabled); + return if ($fs_prefs{ignores} && + $server->masks_match($fs_prefs{ignores}, $sender, $addr)); + + foreach my $qn (0 .. $#fs_queues) { + next if ($fs_queues[$qn]{ignores} && + $server->masks_match($fs_queues[$qn]{ignores}, $sender, $addr)); + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + next if (uc($s) ne uc($server->{tag})); + foreach my $channel (split(' ', $fs_queues[$qn]->{channels})) { + next if (uc($channel) ne uc($target)); + + + # trigger typed + if (!$fs_queues[$qn]->{ctcp_only} && + uc($text) eq uc($fs_queues[$qn]->{trigger})) { + try_connecting_user($server, $sender, $qn); + return; + } + + # strip extra spaces + $_ = uc($text); + s/\s+$//; s/^\s+$//; s/\s+/ /g; + if (($_ eq '!LIST') || ($_ eq ('!LIST '.uc($$server{nick}))) || + ($_ eq '!OLIST' and $fs_queues[$qn]->{restricted_level}) || + ($_ eq '!VLIST' and $fs_queues[$qn]->{restricted_level} == 1) + ) { + show_notice($server, $sender, $qn); + } + if (length($fs_queues[$qn]->{request}) && ($_ eq '!REQUEST')) + { + my $msg = "[$fs_prefs{clr_hi}Request$fs_prefs{clr_txt}] ". + "Message:[$fs_prefs{clr_hi}$fs_queues[$qn]->{request}". + "$fs_prefs{clr_txt}] - FServe $VERSION"; + $server->command("^NOTICE $sender $fs_prefs{clr_txt}$msg"); + print_what_we_did("NOTICE $sender $fs_prefs{clr_txt}$msg"); + } + + if ($fs_queues[$qn]->{find}) { + if (/^\@FIND /) { + if ($sender !~ /^#/) { + show_find($server, $sender, $text, $qn); + } + } + } + + } # loop over channels + } # loop over servers + } # loop over queues +} + + +############################################################################### +# updates userinfo on nick changes +############################################################################### +sub sig_nicklist_changed +{ + my ($chan, $nick, $oldnick) = @_; + my $server_tag = $chan->{server}{tag}; + + print_debug("NICK CHANGE: $oldnick -> $nick->{nick}\@$server_tag on $chan->{name}"); + + foreach my $qn (0 .. $#fs_queues) { + + my $ch_ok = 0; + my $srv_ok = 0; + foreach (split(' ', $fs_queues[$qn]->{channels})) { + if (uc($_) eq uc($chan->{name})) { + $ch_ok = 1; + last; + } + } + foreach (split(' ', $fs_queues[$qn]->{servers})) { + if (uc($_) eq uc($server_tag)) { + $srv_ok = 1; + last; + } + } + + next unless ($ch_ok && $srv_ok); + + + my $old_user_id = $oldnick.'@'.$server_tag; + my $user_id = $nick->{nick}.'@'.$server_tag; + + if (defined $fs_users{$old_user_id}) { + print_debug("Changing connected user data"); + # update user data + my $rec = $fs_users{$old_user_id}; + delete $fs_users{$old_user_id}; + $fs_users{$user_id} = { %{$rec} }; + } + + # update queue + my $fsq = $fs_queues[$qn]->{queue}; + foreach (0 .. $#{$fsq}) { + if (${$fsq}[$_]->{nick} eq $oldnick && + ${$fsq}[$_]->{server_tag} eq $server_tag) { + print_debug("Changing queued file data"); + ${$fsq}[$_]->{nick} = $nick->{nick}; + } + } + + # DONT update sends - irssi bug? + # irssi doesn't change nick in dcc sends +# foreach (0 .. $#fs_sends) { +# if ($fs_sends[$_]->{nick} eq $oldnick && +# $fs_sends[$_]->{server_tag} eq $server_tag) { +# $fs_sends[$_]->{nick} = $nick->{nick}; +# } +# } + + } +} + +############################################################################### +# sig_timeout(): called once every second +############################################################################### +sub sig_timeout +{ + # kill connections that said "bye", campers, ghost users etc. + foreach (@kill_dcc) { + my ($nick, $servertag) = split('@', $_); + my $server = Irssi::server_find_tag($servertag); + next if (!$server || !$server->{connected}); + print_debug("Closing dcc chat to $nick on $servertag"); + $server->command("DCC CLOSE CHAT $nick"); + } + @kill_dcc = (); + + my $time = time(); + + # check for campers... + foreach (keys %fs_users) { + $fs_users{$_}{time}++; + if ($fs_users{$_}{status} >= 0) { + $fs_users{$_}{status}++; + my ($nick, $server_tag) = split('@', $_); + + if ($fs_users{$_}{status} > $fs_prefs{idle_time}) { + send_user_msg($server_tag, $nick, + "Idletime ($fs_prefs{clr_hi}". + "$fs_prefs{idle_time}$fs_prefs{clr_txt} sec) ". + "reached, disconnecting!"); + push(@kill_dcc, $_); + } elsif ($fs_users{$_}{time} > $fs_prefs{max_time}) { + send_user_msg($server_tag, $nick, + "Does this look like a campsite? (". + "$fs_prefs{clr_hi}$fs_prefs{max_time} ". + "sec$fs_prefs{clr_txt})"); + push(@kill_dcc, $_); + } + # 7 minutes for user to connect + } elsif ($fs_users{$_}{status} == -1 and $fs_users{$_}{time} > 420) { + print_msg("BUG workaround: probably ghost user '$_'. Removing from user list ."); + delete $fs_users{$_}; + } + } + + return if (! $fs_enabled); + + $online_time++; + + # auto save config file + if ($fs_prefs{auto_save} && $time % $fs_prefs{auto_save} == 0) { + print_debug("Autosaving..."); + save_config(); + save_queue(); + } + + # update all $queue->{sends} + # FIXME: Do this 'the old way' + # FIXME: BUG: since number of sends is computed only every second + # users could exploit this and gain more sends/queues then allowed + foreach (0 .. $#fs_queues) { $fs_queues[$_]->{sends} = 0; } + foreach (0 .. $#fs_sends) { $fs_queues[$fs_sends[$_]->{queue}]->{sends}++; } +# foreach (0 .. $#fs_queues) { +# print_debug("Trigger #" . $_ . " have " . $fs_queues[$_]->{sends} . +# " sends.") ; +# } + + # First send forced sends + my $file_sent = 0; + foreach (0 .. $#fs_queues) { + if ($fs_queues[$_]->{sends} < $fs_queues[$_]->{guaranted_sends}) { + if (run_queue($fs_queues[$_]) == 0) { + $file_sent = 1; + $upload_counter = 0; + print_debug("Sent forced queue"); + last; + } + } + } + + # send only one file per second. + if (!$file_sent) { + if (send_next_file() == 0) { + $file_sent = 1; + $upload_counter = 0; + print_debug("Sent normal queue"); + } + } + + # check for min upload (up to 2*max_sends+1) + # FIXME don't use 2*m_s+1 but parametrize + if (!$file_sent && @fs_sends >= $fs_prefs{max_sends} && + $time > $last_upload_check && + @fs_sends <= 2*$fs_prefs{max_sends} && ($time % 60) == 0) { + my $curr_ups = 0; + foreach my $dcc (Irssi::Irc::dccs()) { + if ($dcc->{type} eq 'SEND') { + $curr_ups += ($dcc->{transfd}-$dcc->{skipped})/($time - $last_upload_check); + } + } + $curr_ups -= $last_upload; + $last_upload += $curr_ups; + $last_upload_check = $time; + if ($curr_ups > 0 && $curr_ups < $fs_prefs{min_upload}) { + $upload_counter++; + print_debug("Upload $curr_ups is below minimal, counter is $upload_counter"); + if ($upload_counter > 4) { + send_next_file(1); + $upload_counter = 0; + } + } else { + $upload_counter = 0; + } + } + + # recache files + if ($fs_prefs{recache_interval} && + $time % $fs_prefs{recache_interval} == 0) { + update_files(); + } + + # notify channels + foreach my $qn (0 .. $#fs_queues) { + if ($fs_queues[$qn]->{notify_interval} && + $time % $fs_queues[$qn]->{notify_interval} == 0) { + foreach (split(' ', $fs_queues[$qn]->{channels})) { + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + my $server = Irssi::server_find_tag($s); + next if (!$server || !$server->{connected}); + show_notice($server, $_, $qn); + } + } + } + } + + # check speed of sends + if (($time % 60) == 0) { + for (my $s = $#fs_sends; $s >= 0; $s--) { + if ($fs_queues[$fs_sends[$s]{queue}]{min_cps}) { + check_send_speed($s); + } + } + } +} + +############################################################################### +# check_send_speed(): aborts send in $slot if speed < $fs_prefs{min_cps} +############################################################################### +sub check_send_speed +{ + my ($s) = @_; + print_debug("check_sends_speed: checking speed of ". + "$fs_sends[$s]->{nick}\@$fs_sends[$s]->{server_tag}". + " $fs_sends[$s]->{file}"); + + foreach my $dcc (Irssi::Irc::dccs()) { + print_debug("check_sends_speed: checking DCC ". + "$dcc->{nick}\@$dcc->{servertag} $dcc->{arg}"); + + next if ($dcc->{type} ne 'SEND' || + $dcc->{nick} ne $fs_sends[$s]->{nick} || + $dcc->{servertag} ne $fs_sends[$s]->{server_tag} || + $dcc->{arg} ne $fs_sends[$s]->{file}); + + print_debug ("Found send"); + return unless ($dcc->{starttime}); + + if (defined $fs_sends[$s]->{transfd}) { + my $speed = ($dcc->{transfd}-$fs_sends[$s]->{transfd})/60; + my $min_cps = $fs_queues[$fs_sends[$s]{queue}]{min_cps}; + if ($speed < 0) { + print_msg("BUG: send speed < 0 ($speed). Send number $s, ". + "dcc->transfd='$dcc->{transfd}', fs_sends->transfd='". + $fs_sends[$s]->{transfd} . "', skipped='". + $dcc->{skipped}. "', starttime='$dcc->{starttime}'. ". + "Please report this to maintainer (the best is to attach ". + "log output of last couple of minutes). Listing sends:"); + display_sends('!fserve!'); + } + if ($speed < $min_cps) { + # too slow... + + if ($fs_sends[$s]->{warns} < + $fs_queues[$fs_sends[$s]{queue}]->{speed_warnings}) { + + # but he/she still has a chanse... + my $warn_msg; + my $last_warn_msg; + + print_debug("$dcc->{nick}: send is too slow ($speed),". + " but warns=".$fs_sends[$s]->{warns}); + + if (!$fs_sends[$s]->{dontwarn}) { + + if ($fs_sends[$s]->{warns} == 0) { + $warn_msg = "First warning"; + } elsif ($fs_sends[$s]->{warns} == 1) { + $warn_msg = "Second warning"; + } else { + $warn_msg = "Warning"; + $fs_sends[$s]->{dontwarn} = 1; + $last_warn_msg = ' Next warnings will be suppressed.'; + } + my $server = $dcc->{server}; + if ($server && $server->{connected}) { + $server->command("^NOTICE $fs_sends[$s]->{nick} ". + $fs_prefs{clr_txt}.$warn_msg. + ": the speed of your send (". + $fs_prefs{clr_hi}.size_to_str($speed)."/s". + $fs_prefs{clr_txt}.") is less than min CPS ". + "requirement (".$fs_prefs{clr_hi}. + size_to_str($min_cps)."/s". + $fs_prefs{clr_txt}.").".$last_warn_msg); + print_what_we_did("NOTICE $fs_sends[$s]->{nick} ". + $fs_prefs{clr_txt}.$warn_msg. + ": the speed of your send (". + $fs_prefs{clr_hi}.size_to_str($speed)."/s". + $fs_prefs{clr_txt}.") is less than min CPS ". + "requirement (".$fs_prefs{clr_hi}. + size_to_str($min_cps)."/s". + $fs_prefs{clr_txt}.").".$last_warn_msg); + } + } + + $fs_sends[$s]->{warns} += 1; + } else { + # we must finish him :( + my $server = $dcc->{server}; + print_debug("$dcc->{nick}: warns=". + $fs_sends[$s]->{warns}. + " and speed is too slow ($speed)"); + if ($server && $server->{connected}) { + $server->command("^NOTICE $fs_sends[$s]->{nick} ". + $fs_prefs{clr_txt}."The speed of your send (". + $fs_prefs{clr_hi}.size_to_str($speed)."/s". + $fs_prefs{clr_txt}.") is less than min CPS ". + "requirement (".$fs_prefs{clr_hi}. + size_to_str($min_cps)."/s". + $fs_prefs{clr_txt}."), aborting..."); + print_what_we_did("NOTICE $fs_sends[$s]->{nick} ". + $fs_prefs{clr_txt}."The speed of your send (". + $fs_prefs{clr_hi}.size_to_str($speed)."/s". + $fs_prefs{clr_txt}.") is less than min CPS ". + "requirement (".$fs_prefs{clr_hi}. + size_to_str($min_cps)."/s". + $fs_prefs{clr_txt}."), aborting..."); + + $fs_sends[$s]{transfd} = -1; + $server->command("DCC CLOSE SEND $dcc->{nick}"); + } + # FIXME: don't return here? + return; # don't touch $fs_sends[$s] anymore! + } + } else { + if ($fs_sends[$s]->{warns}) { + print_debug("$dcc->{nick}: speed is ok ($speed), reset speed warnings"); + $fs_sends[$s]->{warns} = 0; + } + } + } + $fs_sends[$s]->{transfd} = $dcc->{transfd}; + return; + } + # Could not find active send matching out record - delete it + # Don't know why it happens, one possibility is the file name in + # dcc_destroyed do not match the one recoreded in fs_sends, but don't + # know how it's possibile + print_debug("BUG?: cannot find file $fs_sends[$s]->{file} sending to ". + "$fs_sends[$s]->{nick}\@$fs_sends[$s]->{server_tag}"); + print_debug("Active sends:"); + foreach (Irssi::Irc::dccs()) { + print_debug("$_->{nick}\@$_->{servertag} -> $_->{arg}") + if ($_->{type} eq 'SEND'); + } + print_debug("Removing lost send"); + splice(@fs_sends, $s, 1); +} + + +sub do_help +{ + my $arg = lc(join(" ", @_)); + print_msg ("Arg is '$arg'"); + + if (! $arg) { print_msg(" +Help for FServe + +All FServe commands are executed using '/fs <command>' +syntax. +To get more help about specific topic type +'/fs help <topic>'. + +List of available help topics: +* commands - available commands +* tutorial - how to set up simple file server +* bugs - known bugs/limitations (TODO) +"); return; } + + if ($arg eq "commands") { print_msg(" +List of FServe commands. + +To get more help about specific command type +'/fs help <command>'. + +v* on - enable fileserver +v* off - disable fileserver +v* save - save config file +v* load - load config file +v* saveq - save sends and queues +v* loadq - load queues +v* set - list/set global settings +v* sett - list/set trigger variables +v* addt - add new trigger +v* delt - delete trigger +v* selt - set default trigger +v* queue - list file queue +v* sortt - sort trigger +v* move - move queue slots around +* insert - insert a file into queue +* clear - remove queued files +* sends - list active sends +* who - list online online +* stats - show server statistics +* distro - show distro statistics +* recache - update filecache +* notify - show fserve ad to user/channel +* help - show help +"); return; } + + if ($arg eq "on") { print_msg(" +ON + +Enables FServe, updates filecache. +Doesn't load saved queues. + +See also: LOADQ +"); return; } + + if ($arg eq "off") { print_msg(" +OFF + +Disables FServe. +If 'autosave_on_close' is 1 saves sends and queues. + +See also: SAVEQ +"); return; } + + if ($arg eq "save") { print_msg(" +SAVE + +Saves config file. +"); return; } + + if ($arg eq "load") { print_msg(" +LOAD + +Loads config file. +"); return; } + + if ($arg eq "saveq") { print_msg(" +SAVEQ + +Saves sends and queues. + +See also: LOADQ +"); return; } + + if ($arg eq "loadq") { print_msg(" +LOADQ + +Loads sends and queues (sends are put +in the queues as first) + +See also: SAVEQ +"); return; } + + if ($arg eq "set") { print_msg(" +SET [-clear] [variable value] + +If used without arguments lists global settings. + +You can unset variable with -clear switch, +for example: /fs set -clear offline_message + +To get help for specific variable use +/fs help set <variable_name> + +See also: SETT +"); return; } + + if ($arg eq "sett") { print_msg(" +SETT [-clear] [variable value] + +If used without arguments lists current trigger +settings. +You can select current trigger with '/fs selt <number>' + +You can unset variable with -clear switch, +for example: /fs sett -clear offline_message + +To get help for specific variable use +/fs help sett <variable_name> + +See also: SET, SELT +"); return; } + + if ($arg eq "addt") { print_msg(" +ADDT + +Adds new trigger. + +See also: SELT +"); return; } + + if ($arg eq "delt") { print_msg(" +DELT <trigger number> + +Removes trigger. +It does not remove files from queues. + +See also: SELT +"); return; } + + if ($arg eq "selt") { print_msg(" +SELT <trigger number> + +Selects default trigger. + +The default trigger is used as default for +MOVE, QUEUE, SETT, SORTT commands. +"); return; } + + if ($arg eq "queue") { print_msg(" +QUEUE [<trigger number>] + +Displays queued files. +If used without argument uses default trigger. +You can use '*' as an argument to display all +queued files. + +See also: SELT +"); return; } + + if ($arg eq "sortt") { print_msg(" +SORTT [<trigger number>] + +Sorts queued files according to queue_priority. +If used without argument uses default trigger. + +See also: SELT +"); return; } + + if ($arg eq "move") { print_msg(" +MOVE [<trigger number>] <from> <to> + +Moves files queued in trigger <trigger number> (or default +trigger) from position <from> to position <to>. + +See also: SELT +"); return; } + + if ($arg eq "distro") { print_msg(" +DISTRO stats + +Displays send count for files + +See also: SET distro +"); return; } + + if ($arg eq "set auto_save") { print_msg(" +SET auto_save <seconds> + +Every <seconds> seconds saves config, sends and +queues + +See also: SET autosave_on_close +"); return; } + + if ($arg eq "set autosave_on_close") { print_msg(" +SET autosave_on_close 0|1 + +When set to 1 sends and queues will be saved in /fs off + +See also: SET auto_save +"); return; } + + if ($arg =~ /^set clr_(dir|file|hi|txt)$/) { print_msg(" +SET clr_dir <color> +SET clr_file <color> +SET clr_hi <color> +SET clr_txt <color> + +This settings controll colors in fserve. +Currently it's a little bit inconsistent. +You can set <color> using ^C<txt_color>,<bg_color> +(standart irssi/bitchx colors), for example +/SET clr_txt ^C12 +to set text color to blue. + +Remember to use xy color codes, i.e. don't use +^C9 but use ^C09. If not displaying files that start +with a number will be fscked ;) +"); return; } + + if ($arg eq "set count_send_as_queue") { print_msg(" +SET count_send_as_queue 0|1 + +If set to 1 sends user have are counted as queues. +So if user have 1 send and 2 file queued, and +user_slots is set to 3 the user won't be able +to queue any more files (because has 2 queues and +1 send = 3 files). If count_send_as_queue was 0 +the user would be able to queue one more file. + +See also: SETT user_slots +"); return; } + + if ($arg eq "set debug") { print_msg(" +SET debug 0|1 + +When set to 1 enables diagnostic messages +"); return; } + + if ($arg eq "set distro" || $arg eq "set distro_file" ) { print_msg(" +SET distro <probability> +SET distro_file <file_name> + +When <probability> is 1 fileserver counts how many times +each file was sent, and first sends files with lowest send +count. + +In fact, distro setting isn't simply 0/1. It's a PROBABILITY of +using distro mode for the send. The values should be from range +[0,1], where 0 means don't use distro mode at all, and 1 means +allways use distro mode. + +For example when it's set to 0.7 it'll use distro mode in 7 +cases of 10 (more or less). + +See also: DISTRO +"); return; } + + if ($arg eq "set idle_time" || $arg eq "set max_time") { print_msg(" +SET idle_time <s1> +SET max_time <s2> + +Controls how much time the user can be connected with +fserve on dcc chat. + +User will be disconnected after either: +<s1> seconds of inactivity +<s2> seconds since connecting +"); return; } + + if ($arg eq "set ignores" || $arg eq "sett ignores") { print_msg(" +SET ignores <mask> <mask2> ... +SETT ignores <mask> <mask2> ... + +Using this settings you can 'ban' users from the fserve. +Fserve won't respond to !list nor trigger. + +The <mask> is in normal nick!ident\@host format, +you can use '*' and '?'. +"); return; } + + if ($arg eq "set log_name") { print_msg(" +SET log_name <file> + +Logs file transfers to <file> + +You can use \$IRSSI and ~ that specify irssi's home +and your home directory. +"); return; } + + if ($arg eq "set max_queues" || + $arg =~ /^sett (max_queues|guaranted_queues)$/){ print_msg(" +SET max_queues <val> +SETT max_queues <val> +SETT guaranted_queues <val> + +Those setting are responsibile for number of queues for +the trigger and for whole fserve. + +Algorithm used to compute number of free/max queues: + +Maximum queues := + max( guaranted_queues, + min(global max_queues, trigger max_queues) ) + +Free queues := + max( guaranted_queues - number of trigger queues, + min( global max_queues - number of all queues, + trigger max_queues - number of queue queues ) ) + +In short: +a) the trigger has at least guaranted_queues queues +b) maximum number of queues is the smallest value of + global and trigger max_queues, except for (a) + +See also: SET max_sends + +TODO: examples of usage +"); return; } + + if ($arg eq "set max_sends" || + $arg =~ /^sett (max_sends|guaranted_sends)$/){ print_msg(" +SET max_sends <val> +SETT max_sends <val> +SETT guaranted_sends <val> + +Those setting are responsibile for number of sends for +the trigger and for the whole fserve. + +Algorithm used to compute number of free/max sends: + +Maximum sends := + max( guaranted_sends, + min(global max_sends, trigger max_sends) ) + +Free sends := + max( guaranted_sends - number of trigger sends, + min( global max_sends - number of all sends, + trigger max_sends - number of trigger sends ) ) + +In short: +a) the trigger has at least guaranted_sends sends +b) maximum number of sends is the smallest value of + global and trigger max_sends, except for (a) + +See also: SET max_queues, SET min_upload +"); return; } + + if ($arg eq "set max_users") { print_msg(" +SET max_users <number> + +Sets how many users can connect to the fserve. +"); return; } + + if ($arg eq "set min_upload") { print_msg(" +SET min_upload <bps> + +Tries to make sure that sum of upload speeds +of all dcc sends is >= <bps>. If for 4 minutes +it's no it tries to send next file, even if +there is already max_sends sends. +"); return; } + + if ($arg eq "set motd" or $arg eq "set motdfile" or + $arg eq "sett motd") { print_msg(" +SET <motd> +SET <motd_file> +SETT <motd> + +Specifies messages that will be displayed in welcome message +after user connects to fserve. +The message can be read from file <motd_file>. +In <motd_file> you can use \$IRSSI and ~ that specify irssi's +home and your home directory. +"); return; } + + if ($arg eq "set offline_message") { print_msg(" +SET offline_message <message> + +When fserve is offline and user tries to connect +to it using ctcp trigger fserve sends notice: +'Sorry, fserve is currently offline. <message>' +"); return; } + + if ($arg eq "set queuefile") { print_msg(" +SET queuefile <file> + +Saves sends and queues to <file> + +You can use \$IRSSI and ~ that specify irssi's +home and your home directory. +"); return; } + + if ($arg eq "set recache_interval") { print_msg(" +SET recache_interval <seconds> + +Every <seconds> does /fs recache. +"); return; } + + if ($arg eq "sett channels") { print_msg(" +SETT channels <#channel1> [#channel2 ...] + +Space separated list of channels on which this +trigger will work. + +See also: SETT servers +"); return; } + + if ($arg eq "sett content" or $arg eq "sett note") { print_msg(" +SETT content <content> +SETT note <note> + +Text that can be displayed in fserve ad. + +See also: SETT custom_notice +"); return; } + + if ($arg eq "sett ctcp_only") { print_msg(" +SETT ctcp_only 0|1 + +If set to 1 fserve will ignore triggers typed +on channels. It'll only respond to /ctcp. + +If set to 0 it will respond to both triggers typed +on channels and used in /ctcp. +"); return; } + + if ($arg eq "sett custom_notice" || $arg eq "sett custom_notice_fields") { print_msg(" +SETT custom_notice 0|1 +SETT custom_notice_fields <list of fields> + +Controls what will be included in fserver ad. +If custom_notice is 0 then everything is included. +If it's 1 then only fields specified in <list of fields> +will be included. +If it's 1 and custom_notice_fields is empty then fserve +doesn't show ad at all (but it still respond to trigger +etc.) + +Possibile fields: trigger, sends, queues, min_cps, online, +accessed, snagged, record, current_upstream, serving, +note, content + +Example: +/fs sett custom_notice_fields trigger note content +"); return; } + + if ($arg eq "sett dont_notify") { print_msg(" +"); return; } + if ($arg eq "sett find") { print_msg(" +"); return; } + if ($arg eq "sett ignore_msg") { print_msg(" +"); return; } + if ($arg eq "sett instant_send") { print_msg(" +"); return; } + if ($arg eq "sett max_resends") { print_msg(" +"); return; } + if ($arg eq "sett min_cps") { print_msg(" +"); return; } + if ($arg eq "sett nice") { print_msg(" +"); return; } + if ($arg eq "sett notify_interval") { print_msg(" +"); return; } + + if ($arg eq "sett notify_on_join") { print_msg(" +SETT notify_on_join 0|1 + +When on, users joining a served channel will +be sent an fserve notice. +"); return; } + + if ($arg eq "sett queue_priority") { print_msg(" +"); return; } + if ($arg eq "sett request") { print_msg(" +"); return; } + if ($arg eq "sett restricted_level") { print_msg(" +"); return; } + if ($arg eq "sett root_dir") { print_msg(" +"); return; } + + if ($arg eq "sett servers") { print_msg(" +SETT servers <server_tag> [server_tag_2 ...] + +Space separated list of server tags on which this +trigger will work. +Please read tutorial on how to add server tags. + +See also SETT channels, tutorial +"); return; } + + if ($arg eq "sett speed_warnings") { print_msg(" +"); return; } + if ($arg eq "sett trigger") { print_msg(" +"); return; } + + if ($arg eq "sett user_slots") { print_msg(" +SETT user_slots <number> + +Number of file user can queue (sometimes +files being sent counts as well - see +SET count_send_as_queue). + +See also: SET count_send_as_queue +"); return; } + + if ($arg eq "tutorial") { + print_msg(" +Setting up simple file server. + +After loading fserve you need to at least +- add first trigger with '/fs addt' +- set up 'root_dir', 'servers' and 'channels' + For example: + /fs sett root_dir /home/me/fs_root + /fs sett servers aniv + /fs sett channels #smurfs + +The 'aniv' is the name if irc network you'll be using. +You can add irc networks with '/ircnet add', for example: +/ircnet add aniv +and then +/server add -ircnet aniv irc.aniverse.com + +You can now enable the FServe with '/fs on'! + +Some other things you should know: +- you can list global and trigger-specific settings with + '/fs set' and '/fs sett' +- you can add more triggers with '/fs addt' and choose default + trigger with '/fs selt <number>' +- 'servers' and 'channels' can be a list of space separated + values, for example '#smurfs #gumibears #wuzzles' +- '/fs help' has help for all FServe commands and settings +"); + return; + } + + if ($arg eq "bugs") { print_msg(" +Limitations: + +There can be only one send per user on irc server, no matter +how many trigger there are. Maybe this should be changed to +1 send/trigger or even be parametrized. Comments welcomme. +"); return; } + + print_msg("No such help topic: $arg"); +} + +############################################################################## +# Handle an "/fs *" type command +############################################################################### +sub sig_fs_command +{ + my ($cmd_line, $server, $win_item) = @_; + my @args = split(' ', $cmd_line); + + if (@args <= 0 || lc($args[0]) eq 'help') { + shift @args; + do_help(@args); + return; + } + + # convert command to lowercase + my $cmd = lc(shift(@args)); + + if ($cmd eq 'on') { + unless ($fs_enabled) { + update_files(); + $timer_tag = Irssi::timeout_add(1000, 'sig_timeout', 0); + $fs_enabled = 1; + } + print_msg("Fileserver online!"); + } elsif ($cmd eq 'off') { + if ($fs_enabled) { + $fs_enabled = 0; + Irssi::timeout_remove($timer_tag); + print_msg("Sends & Queue saved") + if ($fs_prefs{autosave_on_close} && (!save_queue())); + print_msg("Distro file saved") if ($fs_prefs{distro} and !save_distro()); + } + print_msg("Fileserver offline!"); + } elsif ($cmd eq 'set' || $cmd eq 'sett') { + my $hash; + if ($cmd eq 'set') { + $hash = \%fs_prefs; + } else { + $hash = $fs_queues[$default_queue]; + } + if (@args == 0) { + my $msg = "[$fs_prefs{clr_hi}FServe Variables$fs_prefs{clr_txt}]"; + if ($cmd eq 'sett') { + $msg .= " for queue $default_queue"; + } + print_msg($msg); + foreach (sort(keys %{$hash})) { + if (/clr/) { + print_msg("$_ $fs_prefs{clr_hi}=$fs_prefs{clr_txt} ". + "$hash->{$_}COLOR"); + } elsif ($cmd eq 'sett' && ($_ eq 'queue' || $_ eq 'cache' || + $_ eq 'sends' || $_ eq 'filecount' || $_ eq 'bytecount')) { + next; + } else { + print_msg("$_ $fs_prefs{clr_hi}=$fs_prefs{clr_txt} ". + $hash->{$_}); + } + } + print_msg("\003\n$fs_prefs{clr_txt}Ex: /fs set max_users 4"); + } elsif (@args < 2) { + print_msg("Error: usage /fs $cmd <var> <value>"); + } elsif ($args[0] eq '-clear' && defined $hash->{$args[1]}) { + print_msg("Clearing $args[1]"); + $hash->{$args[1]} = ""; + if ($args[1] eq 'log_name' && $logfp) { + print_log("Closing log."); + close($logfp); + undef $logfp; + } + } elsif (defined $hash->{$args[0]}) { + my $var = shift(@args); + return if ($cmd eq 'sett' && ($var eq 'queue' || $var eq 'cache' || + $var eq 'sends' || $var eq 'filecount' || $var eq 'bytecount')); + $hash->{$var} = "@args"; + if ($var =~ /^clr/) { + print_msg("Setting: $var $fs_prefs{clr_hi}=$hash->{$var}COLOR"); + } else { + print_msg("Setting: $var $fs_prefs{clr_hi}=$fs_prefs{clr_txt} ". + $hash->{$var}); + } + if ($var eq 'log_name') { + if ($logfp) { + print_log("Closing log."); + close($logfp); + undef $logfp; + } + print_log("Opening log."); + } elsif ($var eq 'motdfile') { + $motdfile_modified = 0; + } + } else { + print_msg("Error: unknown variable ($args[0])"); + } + } elsif ($cmd eq 'save') { + print_msg("Config file saved!") if (!save_config()); + } elsif ($cmd eq 'load') { + print_msg("Config file loaded!") if (!load_config()); + } elsif ($cmd eq 'saveq') { + print_msg("Sends & Queue saved!") if (!save_queue()); + } elsif ($cmd eq 'loadq') { + print_msg("Queue loaded!") if (!load_queue()); + } elsif ($cmd eq 'who') { + display_who('!fserve!'); + } elsif ($cmd eq 'recache') { + update_files(); + } elsif ($cmd eq 'queue') { + if (@args < 1) { + display_queue('!fserve!', $default_queue); + } elsif ($args[0] eq '*') { + foreach (0 .. $#fs_queues) { + display_queue('!fserve!', $_); + } + } elsif ($args[0] > $#fs_queues) { + print_msg("Usage /fs queue [<queue>]"); + } else { + display_queue('!fserve!', $args[0]); + } + } elsif ($cmd eq 'sends') { + display_sends('!fserve!'); + } elsif ($cmd eq 'sortt') { + if (@args < 1) { + sort_queue($default_queue); + } elsif ($args[0] > $#fs_queues) { + print_msg("Usage /fs sortt [<queue>]"); + } else { + sort_queue($args[0]); + } + } elsif ($cmd eq 'stats') { + display_stats('!fserve!'); + foreach (0 .. $#fs_queues) { + print_msg("Queue $_: ".scalar(@{$fs_queues[$_]->{queue}}).'/'. + $fs_queues[$_]->{max_queues}." files"); + } + } elsif ($cmd eq 'insert') { + if (@args < 3 || $args[0] > $#fs_queues) { + print_msg("Usage /fs insert <queue> <nick> <file>"); + return; + } + my $qn = shift(@args); + my $nick_id = shift(@args); + srv_queue_file($nick_id, "@args", $qn); + } elsif ($cmd eq 'move') { + if (@args < 2 || (@args > 2 && $args[0] > $#fs_queues)) { + print_msg("Usage /fs move [<queue>] <from> <to>"); + } elsif (@args == 2) { + srv_move_slot($args[0], $args[1], $fs_queues[$default_queue]->{queue}); + } else { + srv_move_slot($args[1], $args[2], $fs_queues[$args[0]]->{queue}); + } + } elsif ($cmd eq 'clear') { + if (@args < 1) { + print_msg("Usage /fs clear <nick> | /fs clear -all"); + return; + } + foreach (0 .. $#fs_queues) { + if ($args[0] eq '-all') { + my @nullqueue = (); + $fs_queues[$_]->{queue} = [ @nullqueue ]; + } else { + clear_queue($args[0], 1, $_); + } + } + } elsif ($cmd eq 'notify') { + return unless ($fs_enabled); + # TODO /fs notify #channel server + # FIXME not working? + foreach my $qn (0 .. $#fs_queues) { + if (@args == 0) { + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + my $server = Irssi::server_find_tag($s); + next if (!$server || !$server->{connected}); + foreach (split(' ', $fs_queues[$qn]->{channels})) { + show_notice($server, $_, $qn); + } + } + } else { + foreach my $s (split(' ', $fs_queues[$qn]->{servers})) { + my $server = Irssi::server_find_tag($s); + next if (!$server || !$server->{connected}); + foreach (@args) { + show_notice($server, $_, $qn) + if ($fs_queues[$qn]->{channels} =~ /.*$_.*/i); + } + } + } + } + } elsif ($cmd eq 'distro') { + if ($args[0] eq 'stats') { + foreach (sort keys %fs_distro) { + foreach my $size (sort keys %{$fs_distro{$_}}) { + print_msg("$_ (".$size." B) $fs_distro{$_}{$size}"); + } + } + } else { + print_msg("Usage: /fs distro stats"); + } + } elsif ($cmd eq 'selt') { + if (@args < 1 || $args[0] > $#fs_queues) { + print_msg("Usage: /fs selt <queue>"); + return; + } + $default_queue = $args[0]; + print_msg("Selecting trigger: $default_queue"); + } elsif ($cmd eq 'addt') { + print_msg("Adding trigger: ".scalar(@fs_queues)); + push (@fs_queues, { %fs_queue_defaults }); + @{$fs_queues[$#fs_queues]->{queue}} = (); + } elsif ($cmd eq 'delt') { + if (@args < 1 || $args[0] > $#fs_queues) { + print_msg("Usage: /fs delt <trigger_no>"); + return; + } elsif (@fs_queues < 2) { + print_msg("You cannot remove last trigger!"); + return; + } + my $qn = $args[0]; + if ($fs_queues[$qn]->{sends}) { + print_msg('There are on-going sends for this trigger,'); + print_msg('please stop them first before removing the trigger.'); + print_msg('(If you think fserve.pl should act differently'); + print_msg('in this case please drop me a mail. Thanks)'); + return; + } + splice (@fs_queues, $qn, 1); + foreach (@fs_sends) { + if ($_->{queue} > $qn) { + $_->{queue}--; + } + } + foreach ($qn .. $#fs_queues) { + foreach my $q (@{$fs_queues[$_]->{queue}}) { + $q->{queue}--; + } + } + if ($default_queue >= $qn) { + $default_queue--; + } + print_msg("Trigger $qn deleted"); + } else { + print_msg("Unrecognized command /fs $cmd"); + } +} + +############################################################################### +############################################################################### +## +## Script subroutines +## +############################################################################### +############################################################################### + +############################################################################### +# initiate_dcc_chat($server, $nick, $qn): inits a dcc chat & sets some +# variables for $nick +############################################################################### +sub initiate_dcc_chat +{ + my ($server, $nick, $qn) = @_; + + print_debug("Initiating DCC CHAT to $nick for queue $qn"); + + my %nickinfo = (); + $nickinfo{status} = -1; + $nickinfo{time} = 0; + $nickinfo{ignore} = 0; + $nickinfo{dir} = '/'; + $nickinfo{queue} = $qn; + $nickinfo{server} = $server->{tag}; + + $fs_users{$nick."@".$server->{tag}} = { %nickinfo }; + $server->command("DCC CHAT $nick"); +} + +############################################################################### +# show_notice($server, $dest, $qn): displays server notice to $dest +# ($dest = #channel or nick) +############################################################################### +sub show_notice +{ + my ($server, $dest, $qn) = @_; + my $queue = $fs_queues[$qn]; + + foreach ($fs_queues[$qn]{dont_notify}) { + return if ($_ eq $dest); + } + + my $msg = "\002(\002FServe Online\002)\002"; + + my @fields_list = ("trigger", "sends", "queues", "min_cps", "online", + "accessed", "snagged", "record", "current_upstream", "serving", + "note", "content"); + + if ($queue->{custom_notice}) { + return if (!$queue->{custom_notice_fields}); # Don't send the ad + @fields_list = split(' ', $queue->{custom_notice_fields}); + } + + foreach (@fields_list) { + /trigger/ && do { + $msg .= " Trigger:(/ctcp $$server{nick} $queue->{trigger})"; + next; + }; + /sends/ && do { + my ($curr_sends, $free_sends, $max_sends) = get_max_sends($qn); + $msg .= " Sends:(".($max_sends-$free_sends)."/$max_sends)"; + next; + }; + /queues/ && do { + my ($curr_queues, $free_queues, $max_queues) = get_max_queues($qn); + $msg .= " Queues:(".($max_queues-$free_queues)."/$max_queues)"; + next; + }; + /min_cps/ && do { + if ($queue->{min_cps}) { + $msg .= ' Min CPS:('.size_to_str($queue->{min_cps}).'/s)'; + } + next; + }; + /online/ && do { + $msg .= ' Online:('.(keys %fs_users)."/$fs_prefs{max_users})"; + next; + }; + /accessed/ && do { + $msg .= " Accessed:($fs_stats{login_count} times)"; + next; + }; + /snagged/ && do { + $msg .= ' Snagged:('.size_to_str($fs_stats{transfd}).' in '. + ($fs_stats{sends_ok}+$fs_stats{sends_fail}).' files)'; + next; + }; + /record/ && do { + if ($fs_stats{record_cps}) { + $msg .= ' Record CPS:('.size_to_str($fs_stats{record_cps}). + '/s by '.$fs_stats{rcps_nick}.')'; + } + next; + }; + /current_upstream/ && do { + my $curr_ups = 0; + foreach my $dcc (Irssi::Irc::dccs()) { + if ($dcc->{type} eq 'SEND') { + $curr_ups += ($dcc->{transfd}-$dcc->{skipped})/ + (time() - $dcc->{starttime} + 1); + } + } + $msg .= ' Current Upstream:('.size_to_str($curr_ups).'/s)'; + next; + }; + /serving/ && do { + $msg .= ' Serving:('.size_to_str($queue->{bytecount}).' in '. + "$queue->{filecount} files)"; + next; + }; + /note/ && do { + if (length($queue->{note})) { + $msg .= " Note:($fs_prefs{clr_hi}$queue->{note}$fs_prefs{clr_txt})"; + } + next; + }; + /content/ && do { + if (length($queue->{content})) { + $msg .= " On FServe:($fs_prefs{clr_hi}$queue->{content}$fs_prefs{clr_txt})"; + } + next; + }; + print_debug("Unknown notice field: $_"); + } + + $msg =~ s/\(/\($fs_prefs{clr_hi}/g; + $msg =~ s/\)/$fs_prefs{clr_txt}\)/g; + + $msg .= " [FServe.pl $VERSION]"; + + if ($dest =~ /^#/) { + $server->command("MSG $dest $fs_prefs{clr_txt}$msg"); + } else { + $server->command("^NOTICE $dest $fs_prefs{clr_txt}$msg"); + print_what_we_did("NOTICE $dest $fs_prefs{clr_txt}$msg"); + } +} + +############################################################################### +# show_find($server, $who, $file, $qn): displays @find notice to $who +############################################################################### +sub show_find +{ + my ($server, $who, $file, $qn) = @_; + + $file =~ s/^\@find //i; + $file = "\Q$file\E"; + $file =~ s/([\\]?[* ])+/.*/g; + + print_debug("requested find patter '$file' in queue $qn"); + # prepare list + my @founds = (); + foreach my $dir (keys %{$fs_queues[$qn]->{cache}}) { + my $files = $fs_queues[$qn]->{cache}{$dir}{files}; + my $sizes = $fs_queues[$qn]->{cache}{$dir}{sizes}; + + $dir =~ s/$/\//; + $dir =~ s/^\/+//; + foreach my $i (0 .. $#{$files}) { + $_ = ${$files}[$i]; +# print_debug("Checking against '$_'"); + if (/$file/i) { # hmm.. check Sysreset response... +# print_debug("This file matches!"); + push (@founds, (scalar(@founds)+1).". File: (". + $fs_prefs{clr_dir}.$dir.$_.$fs_prefs{clr_txt}.") Size:(". + size_to_str(${$sizes}[$i]).")"); + } + } + } + + if (!@founds) { + return; + } + + my ($curr_sends, $free_sends, $max_sends) = get_max_sends($qn); + my ($curr_queues, $free_queues, $max_queues) = get_max_queues($qn); + + my $message = "(\@Find Results) - [FServe.pl $VERSION]"; + $server->command("^MSG $who $message"); + print_what_we_did("MSG $who $message"); + $message = "Found ".@founds." file(s) on trigger:(".$fs_prefs{clr_hi}. + "/ctcp $server->{nick} $fs_queues[$qn]->{trigger}".$fs_prefs{clr_txt}. + ") Sends:(".($max_sends-$free_sends)."/$max_sends)". + " Queues:(".($max_queues-$free_queues)."/$max_queues)"; + $server->command("^MSG $who $message"); + print_what_we_did("MSG $who $message"); + + foreach (0 .. $#founds) { + last if ($_ >= $fs_queues[$qn]->{find}); + $server->command("^MSG $who $founds[$_]"); + print_what_we_did("MSG $who $founds[$_]"); + } + if (@founds > $fs_queues[$qn]->{find}) { + $server->command("^MSG $who Too many results to display!"); + print_what_we_did("MSG $who Too many results to display!"); + } else { + $server->command("^MSG $who End of \@Find."); + print_what_we_did("MSG $who End of \@Find."); + } +} + +############################################################################### +# change_dir($nick, $dir): changes directory for $nick +############################################################################### +sub change_dir +{ + my ($nick, $dir) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $qn = $fs_users{$nick}{queue}; + + $dir =~ s/\x03//g; # remove colors if any + my @dir_fields = (); + unless (substr($dir, 0, 1) eq '/') { + @dir_fields = split('/', $fs_users{$nick}{dir}); + } + + foreach (split('/', $dir)) { + next if ($_ eq '.'); + if ($_ eq '..') { + pop(@dir_fields); + } else { + push(@dir_fields, $_); + } + } + + my $new_dir = '/'.join('/', @dir_fields); + $new_dir =~ s/\/+/\//g; # remove excessive '/' + + if (defined $fs_queues[$qn]->{cache}{$new_dir}) { + $fs_users{$nick}{dir} = $new_dir; + send_user_msg($server_tag, $irc_nick, + "[$fs_prefs{clr_hi}$new_dir$fs_prefs{clr_txt}]"); + } else { + send_user_msg($server_tag, $irc_nick, + "[$fs_prefs{clr_hi}$new_dir$fs_prefs{clr_txt}] doesn't exist!"); + } +} + +############################################################################### +# list_dir($nick): list contents of current directory for $nick +############################################################################### +sub list_dir +{ + my ($nick) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $qn = $fs_users{$nick}{queue}; + my $dir = $fs_queues[$qn]->{cache}{$fs_users{$nick}{dir}}; + my @filelist = (); + + $_ = $fs_users{$nick}{dir}; + s/\/+$//; + send_user_msg($server_tag, $irc_nick, + "Listing [$fs_prefs{clr_hi}$_/*.*$fs_prefs{clr_txt}]"); + + # print the directories sorted + send_user_msg($server_tag, $irc_nick, $fs_prefs{clr_dir}."..") + if ($fs_users{$nick}{dir} ne "/"); + send_user_msg($server_tag, $irc_nick, + $fs_prefs{clr_dir}.$_.$fs_prefs{clr_txt}.'/') + foreach (sort(@{${$dir}{dirs}})); + + # prepare filelist + foreach (0 .. $#{${$dir}{files}}) { + push(@filelist, ${$dir}{files}[$_]." ". + size_to_str(${$dir}{sizes}[$_])); + } + + # print the files sorted + send_user_msg($server_tag, $irc_nick, $fs_prefs{clr_file}.$_) + foreach(sort(@filelist)); + send_user_msg($server_tag, $irc_nick, + "End [$fs_prefs{clr_hi}$fs_users{$nick}{dir}$fs_prefs{clr_txt}]"); +} + +############################################################################### +# srv_queue_file($nick_id, $file, $qn): queues to queue $qn file for $nick_id, +# server use only +# (no max_queue and/or duplicate check) +############################################################################### +sub srv_queue_file +{ + my ($nick_id, $path, $qn) = @_; + my ($nick, $server_tag) = split('@', $nick_id); + $path =~ s/~/$ENV{"HOME"}/; + + unless (-e $path || -f $path) { + print_msg("Invalid file: '$path'"); + return; + } + + my $size = (stat($path))[7]; + $path =~ /(.*)\/(.*)/; + $path = $1; + my $file = $2; + + push(@{$fs_queues[$qn]->{queue}}, { queue => $qn, nick => $nick, + file => $file, size => $size, + dir => $path, resends => 0, warns => 0, server_tag => $server_tag }); + + print_msg($fs_prefs{clr_hi}.'#'.@{$fs_queues[$qn]->{queue}}. + $fs_prefs{clr_txt}.": Queuing '$fs_prefs{clr_hi}$file". + "$fs_prefs{clr_txt}' for $fs_prefs{clr_hi}$nick". + "$fs_prefs{clr_txt} ($server_tag) in queue ". + "$fs_prefs{clr_hi}$qn$fs_prefs{clr_txt}!"); +} + +############################################################################### +# srv_move_slot($slot, $dest, [ @queue ]): moves queue slots around +############################################################################### +sub srv_move_slot +{ + my ($slot, $dest, $fsq) = @_; + + $slot--; + $dest--; + + unless (defined ${$fsq}[$slot] || defined ${$fsq}[$dest]) { + print_msg("Error: Invalid slot numbers!"); + return; + } + print_debug("srv_move_slot: Will move $slot to $dest"); + + my %rec = %{${$fsq}[$slot]}; + splice(@{$fsq}, $slot, 1); + splice(@{$fsq}, $dest, 0, { %rec }); + + print_msg("Moved slot $fs_prefs{clr_hi}#".($slot+1).$fs_prefs{clr_txt}. + " to $fs_prefs{clr_hi}#".($dest+1)); +} + +############################################################################### +# get_user_flag($server, $nick,$qn): returns highest user flag +# (normal/voice/halfop/op) among all channels from fs_queues[$qn]->{channels} +############################################################################### +sub get_user_flag { + my ($server,$nick,$qn) = @_; + + my $bestflag = "normal"; + foreach my $channelName (split(' ', $fs_queues[$qn]->{channels})) { + my $channel = $server->channel_find($channelName); + next if !$channel; + my $n = $channel->nick_find($nick); + next if !$n; + if ($n->{op}) { + return "op"; + } elsif ($n->{halfop}) { + $bestflag = "halfop"; + } elsif ($n->{voice} and $bestflag ne "halfop") { + $bestflag = "voice"; + } + # max 4 categories - see sort_queue() also + } + return $bestflag; +} + +############################################################################### +# sort_queue($qn): sorts queue according to queue_priority +# returns where was moved last position +############################################################################### + # queue_priority format: + # group1 group2 ... groupN + # where groupX is one of: others, normal, voice, halfop, op + # for example: + # normal voice others + # means that first in queue are "normal" people, then people who are +v, + # and then the rest - ops and halfops + # + # When some server is disconnected then all people on this server are + # sorted last in the queue. +sub sort_queue { + my ($qn) = @_; + + print_debug ("sort_queue: $qn"); + return ($#{$fs_queues[$qn]->{queue}}) + if (!$fs_queues[$qn]->{queue_priority}); + + my %prio; + my $n = 1; # highest priority is 0 - resended queue + foreach (split (/ +/, $fs_queues[$qn]->{queue_priority})) { + if (/others/) { + foreach my $type ("normal", "voice", "halfop", "op") { + if (not exists $prio{$type}) { + $prio{$type} = $n; + } + } + } else { + $prio{$_} = $n; + } + $n++; + } + # in case there is no 'others' in queue_priority we assume it's last + foreach my $type ("normal", "voice", "halfop", "op") { + if (not exists $prio{$type}) { + $prio{$type} = $n; + } + } + my $max_prio = $n; + + my @uprio = (0, 0, 0, 0, 0); # assume max 4 categories + resends :) + my $fsq = $fs_queues[$qn]->{queue}; + my $dmsg = 'Sorting...'; + # now do sorting + foreach (0 .. $#{$fsq}) { + if (${$fsq}[$_]->{resends}) { + $n = 0; + } else { + my $server = Irssi::server_find_tag(${$fsq}[$_]->{server_tag}); + if (!$server || !$server->{connected}) { + $n = $max_prio; + } else { + $n = $prio{get_user_flag($server, ${$fsq}[$_]->{nick}, $qn)}; + } + } + + # re-sort these positions 0 .. $_ + splice(@{$fsq}, $uprio[$n], 0, splice(@{$fsq}, $_, 1)) + if ($uprio[$n] != $_); + + $dmsg .= " $_:$uprio[$n]"; + # update @uprio + $uprio[$_]++ foreach ($n .. $#uprio); + } + print_debug($dmsg); + + # $n now has prio for last moved position + return $uprio[$n]-1; +} + +############################################################################### +# queue_file($nick, $file): queues $file for $nick. +############################################################################### +sub queue_file +{ + my ($nick, $ufile) = @_; + $ufile =~ s/\s+$//; + my $qn = $fs_users{$nick}{queue}; + my ($file, $size); + my ($irc_nick, $server_tag) = split('@', $nick); + + print_debug("queue_file: '$ufile' for $nick in queue $qn"); + # try to find the filename in cache + my $files = $fs_queues[$qn]->{cache}{$fs_users{$nick}{dir}}{files}; + my $sizes = $fs_queues[$qn]->{cache}{$fs_users{$nick}{dir}}{sizes}; + + my $fsq = $fs_queues[$qn]->{queue}; + + foreach (0 .. $#{$files}) { + if (uc(${$files}[$_]) eq uc($ufile)) { + $file = ${$files}[$_]; + $size = ${$sizes}[$_]; + last; + } + } + + unless (defined $file) { + send_user_msg($server_tag, $irc_nick, + "Invalid filename: '$fs_prefs{clr_hi}$ufile$fs_prefs{clr_txt}'!"); + return; + } + + my $server = Irssi::server_find_tag($server_tag); + if (!$server || !$server->{connected}) { + print_msg("Error: this should never happen!!! #002"); + return; + } + + if ($size <= $fs_queues[$qn]{instant_send}) { + my $sfile = $fs_queues[$qn]->{root_dir}.$fs_users{$nick}{dir}.'/'.$file; + $sfile =~ s/\/+/\//g; + if (-e $sfile && -f $sfile) { + send_user_msg($server_tag, $irc_nick, + "Sending '$fs_prefs{clr_hi}$file$fs_prefs{clr_txt}'"); + $sfile =~ s/'/\\'/g; + $server->command("DCC SEND $irc_nick $FD$sfile$FD"); + return; + } + } + + my ($curr_queues, $free_queues, $max_queues) = get_max_queues($qn); + my ($curr_sends, $free_sends, $max_sends) = get_max_sends($qn); + + if (count_user_files($server_tag, $irc_nick, $qn) >= + $fs_queues[$qn]->{user_slots}) { + send_user_msg($server_tag, $irc_nick, + "No sends are available and you have ". + "used all your queue slots ($fs_prefs{clr_hi}". + "$fs_queues[$qn]->{user_slots}$fs_prefs{clr_txt})"); + return; + } elsif ($free_queues <= 0) { + send_user_msg($server_tag, $irc_nick, + "No send or queue slots are available!"); + return; + } else { + foreach (0 .. $#{$fsq}) { + if (${$fsq}[$_]->{nick} eq $irc_nick && + ${$fsq}[$_]->{file} eq $file && + ${$fsq}[$_]->{server_tag} eq $server_tag) { + send_user_msg($server_tag, $irc_nick, + "You have already queued '". + "$fs_prefs{clr_hi}$file$fs_prefs{clr_txt}'". + " in slot #$fs_prefs{clr_hi}".($_+1). + "$fs_prefs{clr_txt}!"); + return; + } + } + } + + push(@{$fsq}, { queue => $qn, nick => $irc_nick, file => $file, + size => $size, dir => $fs_queues[$qn]->{root_dir}.$fs_users{$nick}{dir}, + resends => 0, warns => 0, server_tag => $server_tag }); + + my $place = sort_queue($qn); + print_debug("queue_file: queued on place $place"); + + send_user_msg($server_tag, $irc_nick, + "Queued '$fs_prefs{clr_hi}$file$fs_prefs{clr_txt}". + "' (".$fs_prefs{clr_hi}.size_to_str($size). + $fs_prefs{clr_txt}.") in slot ".$fs_prefs{clr_hi}.'#'. + ($place+1) .$fs_prefs{clr_txt}); +} + +############################################################################### +# dequeue_file($nick, $slot): dequeues file in slot $slot for $nick +############################################################################### +sub dequeue_file +{ + my ($nick, $slot) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $fsq = $fs_queues[$fs_users{$nick}{queue}]->{queue}; + + $slot -= 1; + if (defined ${$fsq}[$slot]) { + if (${$fsq}[$slot]->{nick} eq $irc_nick && + ${$fsq}[$slot]->{server_tag} eq $server_tag) { + my $filename = ${$fsq}[$slot]{file}; + splice(@{$fsq}, $slot, 1); + send_user_msg($server_tag, $irc_nick, "Removing '$fs_prefs{clr_hi}". + "$filename$fs_prefs{clr_txt}', you now have $fs_prefs{clr_hi}". + count_queued_files($server_tag, $irc_nick,$fs_users{$nick}{queue}). + "$fs_prefs{clr_txt} file(s) queued!"); + } else { + send_user_msg($server_tag, $irc_nick, + "You can't dequeue other peoples files!!!"); + } + } else { + send_user_msg($server_tag, $irc_nick, + "Queue slot $fs_prefs{clr_hi}#".($slot+1). + $fs_prefs{clr_txt}." doesn't exist!"); + } +} + +############################################################################### +# clear_queue($nick, $is_server, $qn): clears all queued files for $nick +############################################################################### +sub clear_queue +{ + my ($nick, $is_server, $qn) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $fsq = $fs_queues[$qn]->{queue}; + my $count = 0; + + if (count_queued_files($server_tag, $irc_nick, $qn) == 0) { + if ($is_server) { + print_msg("$fs_prefs{clr_hi}$nick$fs_prefs{clr_txt} doesn't ". + "have any files queued!"); + } else { + send_user_msg($server_tag, $irc_nick, "You don't have any queued files!"); + } + } else { + for (my $i = $#{$fsq}; $i >= 0; $i--) { + if (${$fsq}[$i]->{nick} eq $irc_nick && + ${$fsq}[$i]->{server_tag} eq $server_tag) { + splice(@{$fsq}, $i, 1); + $count++; + } + } + + $irc_nick = '!fserve!' if ($is_server); + send_user_msg($server_tag, $irc_nick, + "Successfully dequeued $fs_prefs{clr_hi}". + "$count$fs_prefs{clr_txt} file(s)!"); + } +} + +############################################################################### +# display_queue($nick, $qn): displays queue to $nick +############################################################################### +sub display_queue +{ + my ($nick, $qn) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $queue = $fs_queues[$qn]; + my $fsq = $queue->{queue}; + my $m_server = (split(' ', $queue->{servers}) > 1); + + my ($curr_queues, $free_queues, $max_queues) = get_max_queues($qn); + if ($nick eq '!fserve!') { + send_user_msg($server_tag, $irc_nick, + "$curr_queues/$free_queues/$max_queues Current/Free/Max queues ". + "for trigger #".$qn.":"); + } else { + send_user_msg($server_tag, $irc_nick, + $fs_prefs{clr_hi}.$curr_queues.$fs_prefs{clr_txt}."/". + $fs_prefs{clr_hi}.$max_queues.$fs_prefs{clr_txt}. + " file(s) queued for this trigger. ".$fs_prefs{clr_hi}. + $free_queues.$fs_prefs{clr_txt}." free slot(s) left."); + } + + foreach (0 .. $#{$fsq}) { + my $msg = " $fs_prefs{clr_hi}#".($_+1)."$fs_prefs{clr_txt}". + ": $fs_prefs{clr_hi}${$fsq}[$_]->{nick}$fs_prefs{clr_txt}". + ($m_server?" (${$fsq}[$_]->{server_tag})":""). + " queued $fs_prefs{clr_hi}${$fsq}[$_]->{file}$fs_prefs{clr_txt}". + " (".$fs_prefs{clr_hi}.size_to_str(${$fsq}[$_]->{size}). + $fs_prefs{clr_txt}.")"; + if (${$fsq}[$_]->{resends}) { + $msg .= " (Resend #".${$fsq}[$_]->{resends}.")"; + } + send_user_msg($server_tag, $irc_nick, $msg); + } +} + +############################################################################### +# display_who($user_id): shows users connected to $user_id +############################################################################### +sub display_who +{ + my ($user_id) = @_; + my ($nick, $server_tag) = split('@', $user_id); + + send_user_msg($server_tag, $nick, $fs_prefs{clr_hi}.keys(%fs_users). + $fs_prefs{clr_txt}.' user(s) online!'); + + foreach (keys(%fs_users)) { + my ($n, $s_tag) = split('@', $_); + if ($fs_users{$_}{status} == -1) { + send_user_msg($server_tag, $nick, + " $fs_prefs{clr_hi}$n$fs_prefs{clr_txt} ($s_tag):". + " connecting..."); + } else { + send_user_msg($server_tag, $nick, + " $fs_prefs{clr_hi}$n$fs_prefs{clr_txt} ($s_tag):". + " online $fs_prefs{clr_hi}$fs_users{$_}{time}s". + "$fs_prefs{clr_txt} idle: $fs_prefs{clr_hi}". + "$fs_users{$_}{status}s"); + } + } +} + +############################################################################### +# display_sends($nick): shows active sends to $nick +############################################################################### +sub display_sends +{ + my ($nick) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $guaranted_sends; + my $qtext = ""; + my $qn = -1; + + if (defined $fs_users{$nick}) { + $qn = $fs_users{$nick}{queue}; + } + + + if ($qn != -1) { # user - show only this queue sends + my ($curr_sends, $free_sends, $max_sends) = get_max_sends($qn); + send_user_msg($server_tag, $irc_nick, + "Sending $fs_prefs{clr_hi}".$curr_sends.'/'. + $max_sends.$fs_prefs{clr_txt}." file(s) for this trigger. ". + $fs_prefs{clr_hi}.$free_sends.$fs_prefs{clr_txt}." free sends left."); + } else { # me - show all sends + send_user_msg($server_tag, $irc_nick, + "Sending $fs_prefs{clr_hi}".@fs_sends.'/'. + $fs_prefs{max_sends}.$fs_prefs{clr_txt}." file(s)!"); + } + + foreach my $dcc (Irssi::Irc::dccs()) { + next if ($dcc->{type} ne 'SEND'); + + foreach (0 .. $#fs_sends) { + next if ($dcc->{nick} ne $fs_sends[$_]{nick} || + $dcc->{arg} ne $fs_sends[$_]{file} || + $dcc->{servertag} ne $fs_sends[$_]{server_tag}); + + if ($qn < 0) { + $qtext = " for queue #".$fs_sends[$_]->{queue}; + } else { + last if ($fs_sends[$_]->{queue} != $qn); + } + + if ($dcc->{starttime} == 0 || + ($dcc->{transfd}-$dcc->{skipped}) == 0) { + send_user_msg($server_tag, $irc_nick, + " $fs_prefs{clr_hi}#".($_+1). + "$fs_prefs{clr_txt}: Waiting for ". + $fs_prefs{clr_hi}.$dcc->{nick}.$fs_prefs{clr_txt}. + " ($dcc->{servertag}) to accept $fs_prefs{clr_hi}". + "$dcc->{arg}". + $fs_prefs{clr_txt}." (".$fs_prefs{clr_hi}. + size_to_str($fs_sends[$_]->{size}). + $fs_prefs{clr_txt}.")".$qtext); + last; + } + + my $perc = sprintf("%.1f%%", ($dcc->{transfd}/$dcc->{size})*100); + my $speed = ($dcc->{transfd}-$dcc->{skipped})/(time() - $dcc->{starttime} + 1); + my $left = ($dcc->{size} - $dcc->{transfd}) / $speed; + send_user_msg($server_tag, $irc_nick, + " $fs_prefs{clr_hi}#".($_+1)."$fs_prefs{clr_txt}:". + " $fs_prefs{clr_hi}$dcc->{nick}$fs_prefs{clr_txt} ". + "($dcc->{servertag}) has ". + $fs_prefs{clr_hi}.$perc.$fs_prefs{clr_txt}. + " of '$fs_prefs{clr_hi}$dcc->{arg}$fs_prefs{clr_txt}'". + " at ".$fs_prefs{clr_hi}.size_to_str($speed)."/s". + $fs_prefs{clr_txt}." (".$fs_prefs{clr_hi}. + time_to_str($left).$fs_prefs{clr_txt}." left)". + $qtext); + last; + } + } + +} + +############################################################################### +# display_stats($nick): displays server statistics to $nick +############################################################################### +sub display_stats +{ + my ($nick) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + + send_user_msg($server_tag, $irc_nick, "-=[ Server Statistics ]=-"); + send_user_msg($server_tag, $irc_nick, " Online for ".$fs_prefs{clr_hi}.time_to_str($online_time)); + send_user_msg($server_tag, $irc_nick, " Access Count: ".$fs_prefs{clr_hi}.$fs_stats{login_count}); + send_user_msg($server_tag, $irc_nick, " "); + send_user_msg($server_tag, $irc_nick, " Successful Sends: ".$fs_prefs{clr_hi}.$fs_stats{sends_ok}); + send_user_msg($server_tag, $irc_nick, " Bytes Transferred: ".$fs_prefs{clr_hi}.size_to_str($fs_stats{transfd})); + send_user_msg($server_tag, $irc_nick, " Failed Sends: ".$fs_prefs{clr_hi}.$fs_stats{sends_fail}); + send_user_msg($server_tag, $irc_nick, " Record CPS: ".$fs_prefs{clr_hi}.size_to_str($fs_stats{record_cps})."/s"); +} + +############################################################################### +## Shows a small file to the user +############################################################################### +sub display_file ($$) { + my ($nick, $ufile) = @_; + my ($irc_nick, $server_tag) = split('@', $nick); + my $queue = $fs_queues[$fs_users{$nick}{queue}]; + my ($file, $size, $dir, $filepath); + + # try to find the filename in cache + my $files = $queue->{cache}{$fs_users{$nick}{dir}}{files}; + my $sizes = $queue->{cache}{$fs_users{$nick}{dir}}{sizes}; + + foreach (0 .. $#{$files}) { + if (uc(${$files}[$_]) eq uc($ufile)) { + $file = ${$files}[$_]; + $size = ${$sizes}[$_]; + last; + } + } + + $dir = $queue->{root_dir} . $fs_users{$nick}{dir}; + $filepath = "$dir" . "/" . "$ufile"; + + unless (defined $file) { + send_user_msg($server_tag, $irc_nick, "Invalid filename: " . + "'$fs_prefs{clr_hi}$ufile$fs_prefs{clr_txt}'!"); + return; + } + + if ($size > 30000) { + send_user_msg($server_tag, $irc_nick, "File too large: " . + "'$fs_prefs{clr_hi}$ufile$fs_prefs{clr_txt}'!"); + return; + } + + unless (open (RFILE, "<", $filepath)) { + send_user_msg($server_tag, $irc_nick, "Couldn't open file: " . + "'$fs_prefs{clr_hi}$ufile$fs_prefs{clr_txt}'!"); + print_msg("Could not open file $filepath"); + return; + } + + while (my $line = <RFILE>) { + chomp $line; + send_user_msg($server_tag, $irc_nick, $line); + } + + unless (close (RFILE)) { + print_debug("Couldn't close file: $filepath"); + return; + } + + return 1; +} + +############################################################################### +# send_next_file(): send a file from not forced queues +############################################################################### +sub send_next_file +{ + my ($ignore_free_sends) = @_; + + # first step: reorder queues + my @que_numb = (0 .. $#fs_queues); + splice (@que_numb, 0, 0, (splice(@que_numb, $next_queue))); + + # First use queues with lowest 'nice', then queues with least sends. + my @min_queue = sort { + $fs_queues[$a]->{nice} <=> $fs_queues[$b]->{nice} or + $fs_queues[$a]->{sends} <=> $fs_queues[$b]->{sends} + } @que_numb; + + # step 2b: select a queue + foreach my $i (@min_queue) { + my $free_sends = (get_max_sends($i))[1]; + next if ($free_sends == 0 and !$ignore_free_sends); + + + if (!run_queue($fs_queues[$i])) { + $next_queue++; + $next_queue = 0 if ($next_queue >= scalar(@fs_queues)); + print_debug("send_next_file(): next queue will be $next_queue"); + return 0; + } + } + return 1; +} + +############################################################################### +# run_queue($queue): try to send the next file in $queue +############################################################################### +sub run_queue +{ + my ($queue) = @_; + my %entry = (); + my ($next, $nextcount, $nextfile) = (-1); + + # step through the queue + for (my $i = 0; $i < @{$queue->{queue}}; ) { + %entry = %{ ${$queue->{queue}}[$i] }; + my $server = Irssi::server_find_tag($entry{server_tag}); + if (!$server || !$server->{connected}) { + $i++; + next; + } + + my $in_channel = user_in_channel($server, $entry{nick}, $queue); + my $send_active = send_active_for($entry{server_tag}, $entry{nick}); + my $file = $entry{dir}.'/'.$entry{file}; + $file =~ s/\/+/\//g; + + # rand() returns [0,1) so if distro is == 0 this is always false, + # and if distro == 1 this is allways true + my $use_distro = (rand() < $fs_prefs{distro}) ? 1 : 0; + + # send file if user in channel and has no sends active + if (!$send_active && $in_channel && -e $file && -f $file) { + if (!$use_distro) { + $next = $i; + $nextfile = $file; + last; + } + my $count = $fs_distro{$entry{file}}{$entry{size}}; + if ($next < 0 or $nextcount > $count) { + $next = $i; + $nextcount = $count; + $nextfile = $file; + } + $i++; + next; + } + + # remove entry if user wasn't in channel of file didn't exist + if (!$send_active) { + Irssi::print("User $fs_prefs{clr_hi}$entry{nick} ". + "$fs_prefs{clr_txt} not in channel or file doesn't exists,". + " removing $entry{file}". + $fs_prefs{clr_txt}." from queue..."); + splice(@{$queue->{queue}}, $i, 1); + # next slot will have same index + } else { + $i++; + } + } + + return 1 if ($next == -1); + + %entry = %{ ${$queue->{queue}}[$next] }; + my $server = Irssi::server_find_tag($entry{server_tag}); + $server->command("^NOTICE $entry{nick} ".$fs_prefs{clr_txt}. + "Sending you your queued file (".$fs_prefs{clr_hi}. + size_to_str($entry{size}).$fs_prefs{clr_txt}.")"); + print_what_we_did("NOTICE $entry{nick} ".$fs_prefs{clr_txt}. + "Sending you your queued file (".$fs_prefs{clr_hi}. + size_to_str($entry{size}).$fs_prefs{clr_txt}.")"); + $nextfile =~ s/'/\\'/g; + $server->command("DCC SEND $entry{nick} $FD$nextfile$FD"); + push(@fs_sends, { %entry }); + splice(@{$queue->{queue}}, $next, 1); + return 0; +} + +############################################################################### +# update_files(): update the cache from $fs_prefs{root_dir} +############################################################################### +sub update_files +{ + my $filecount; + my $bytecount; + + print_msg("Caching files, please wait!"); + # update the cache + foreach my $qn (0 .. $#fs_queues) { + delete $fs_queues[$qn]->{cache}; + cache_dir($fs_queues[$qn]->{root_dir},$fs_queues[$qn]); + + $filecount = 0; + $bytecount = 0; + foreach my $dir (keys %{$fs_queues[$qn]->{cache}}) { + $filecount += @{$fs_queues[$qn]->{cache}{$dir}{files}}; + $bytecount += $_ foreach (@{$fs_queues[$qn]->{cache}{$dir}{sizes}}); + } + + $fs_queues[$qn]->{filecount} = $filecount; + $fs_queues[$qn]->{bytecount} = $bytecount; + + print_msg("Queue $qn: cached $filecount file(s) (".size_to_str($bytecount).") in ". + (keys(%{$fs_queues[$qn]->{cache}}))." dir(s)!"); + } +} + +############################################################################### +# cache_dir($dir): recursive filecaching subroutine +############################################################################### +sub cache_dir +{ + my ($dir, $queue) = @_; + my @dirs = (); + my @files = (); + my @sizes = (); + + opendir($dir, "$dir"); + while (my $entry = readdir($dir)) { + if (!($entry eq '.') && !($entry eq '..')) { + my $full_path = $dir.'/'.$entry; + if (-d $full_path) { + push(@dirs, $entry); + cache_dir($full_path, $queue); + } elsif (-f $full_path) { + push(@sizes, (stat($full_path))[7]); + push(@files, $entry); + } + } + } + + closedir($dir); + + $dir =~ s/$queue->{root_dir}//; + $dir = '/' if (length($dir) == 0); + + $queue->{cache}{$dir} = { dirs => [ @dirs ], files => [ @files ], + sizes => [ @sizes ] }; +} + +############################################################################### +# count_queued_files($server_tag, $nick,$qn): returns number of queued files +# for $nick +############################################################################### +sub count_queued_files +{ + my ($server_tag, $nick, $qn) = @_; + my $count = 0; + + foreach (0 .. $#{$fs_queues[$qn]->{queue}}) { + $count++ + if (${$fs_queues[$qn]->{queue}}[$_]->{nick} eq $nick && + ${$fs_queues[$qn]->{queue}}[$_]->{server_tag} eq $server_tag); + } + + return $count; +} + +############################################################################### +# count_user_files($server_tag, $nick, $qn): returns number of queued and +# sended files for $nick +############################################################################### +sub count_user_files { + my ($server_tag, $nick, $qn) = @_; + + if (!$fs_prefs{count_send_as_queue}) { + return count_queued_files($server_tag, $nick, $qn); + } + + my $count = count_queued_files($server_tag, $nick, $qn); + foreach (0 .. $#fs_sends) { + $count++ + if ($fs_sends[$_]->{nick} eq $nick && + $fs_sends[$_]->{server_tag} eq $server_tag); + } + + return $count; +} + +############################################################################### +# send_active_for($server_tag, $nick): true if currently sending file to +# $nick +############################################################################### +sub send_active_for +{ + my ($server_tag, $nick) = @_; + + foreach (0 .. $#fs_sends) { + return 1 if ($fs_sends[$_]{nick} eq $nick && + $fs_sends[$_]{server_tag} eq $server_tag); + } + + return 0; +} + +############################################################################### +# user_in_channel($server,$nick,$queue): true if user is on any +# $queue->{channels} +############################################################################### +sub user_in_channel +{ + my ($server, $nick, $queue) = @_; + + foreach (split(' ', $queue->{channels})) { +# print_debug("Checking channel $_"); + my $channel = $server->channel_find($_); + if ($channel && $channel->{joined} && $channel->nick_find($nick)) { + return 1; + } + } + + return 0; +} + +############################################################################### +# send_user_msg($servertag, $nick, $msg): sends a msg to $nick using dcc if +# available +############################################################################### +sub send_user_msg +{ + my ($servertag, $nick, $msg) = @_; + + if ($nick eq "!fserve!") { + print_msg($msg); + } else { + my $server = Irssi::server_find_tag($servertag); + if (!$server || !$server->{connected}) { + return; + } + + my $cmd = ((defined $fs_users{$nick."@".$servertag})?"MSG =$nick":"MSG $nick"); + $server->command("$cmd $fs_prefs{clr_txt}$msg"); + } +} + +############################################################################### +# size_to_str($size): returns a formatted size string +############################################################################### +sub size_to_str +{ + my ($size) = @_; + + if ($size < 1024) { + $size = int($size) . " B"; + } elsif ($size < 1048576) { + $size = sprintf("%.1f kB", $size/1024); + } elsif ($size < 1073741824) { + $size = sprintf("%.2f MB", $size/1048576); + } elsif ($size < 1099511627776) { + $size = sprintf("%.2f GB", $size/1073741824); + } else { + $size = sprintf("%.3f TB", $size/1099511627776); + } + + return $size; +} + +############################################################################### +# time_to_str($time): returns a formatted time string +############################################################################### +sub time_to_str +{ + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime(shift(@_)); + + return sprintf("%dd %dh %dm %ds", $yday, $hour, $min, $sec) if ($yday); + return sprintf("%dh %dm %ds", $hour, $min, $sec) if ($hour); + return sprintf("%dm %ds", $min, $sec) if ($min); + return sprintf("%ds", $sec); +} + +############################################################################### +# save_config(): saves preferences & statistics to file +############################################################################### +sub save_config +{ + my $f = $conffile; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + if (!open(FILE, ">", $f)) { + print_msg("Unable to open $f for writing!"); + return 1; + } + + print (FILE "[ConfigFileVersion 1.0]\n"); + + # save preferences + print(FILE "[common]\n"); + foreach (sort(keys %fs_prefs)) { + print(FILE "$_=$fs_prefs{$_}\n"); + } + + # save statistics + print(FILE "[stats]\n"); + foreach (sort(keys %fs_stats)) { + print(FILE "$_=$fs_stats{$_}\n"); + } + + #save queues settings + foreach my $qn (0 .. $#fs_queues) { + print(FILE "[queue $qn]\n"); + foreach (sort(keys %{$fs_queues[$qn]})) { + next if ($_ eq 'queue' || $_ eq 'cache' || $_ eq 'sends' || + $_ eq 'filecount' || $_ eq 'bytecount'); + print(FILE "$_=$fs_queues[$qn]->{$_}\n"); + } + } + + close(FILE); + return 0; +} + +############################################################################### +# load_distro($file) +############################################################################### +sub load_distro { + my $file = $_[0]; + if (!open(FILE, "<", $file)) { + print_msg("Unable to open $file for reading!"); + return 0; + } + + # file format: + # sent_count file_size file_name + + my ($count, $size, $name); + while (<FILE>) { + chomp; + ($count, $size, $name) = split(/ /, $_, 3); + if (($count !~ /\d+/) or ($size !~ /\d+/) or (!$name)) { + print_msg("Error in $file in line $."); + close(FILE); + return 0; + } + $fs_distro{$name}{$size} = $count; + } + + close(FILE); + return 1; # ok +} + + +############################################################################### +# save_distro() +############################################################################### +sub save_distro +{ + return 0 if (!$fs_prefs{distro_file}); + + my $f = $fs_prefs{distro_file}; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + + if (!open(FILE, ">", $f)) { + print_msg("Unable to open $f for writing!"); + return 1; + } + + foreach (sort keys %fs_distro) { + foreach my $size (sort keys %{$fs_distro{$_}}) { + print FILE "$fs_distro{$_}{$size} $size $_\n"; + } + } + + close(FILE); + return 0; +} + +############################################################################### +# load_config(): loads preferences & statistics from file +############################################################################### +sub load_config +{ + + my $f = $conffile; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + if (!open(FILE, "<", $f)) { + print_msg("Unable to open $f for reading!"); + return 1; + } + + local $/ = "\n"; + + my $config_version = <FILE>; + chomp $config_version; + if ($config_version !~ /^\[ConfigFileVersion 1\.[0-9]+]$/) { + print_msg("Config file format not recognized!"); + print_msg("FServe 2.0 and newer won't work with config file"); + print_msg(" created by earlier versions on FServe."); + return 1; + } + + my $hash = \%fs_prefs; + my %garbage = (); + + while (<FILE>) { + chomp; + if (/^\[(.*)\]$/) { # next chapter + if ($1 eq "common") { + $hash = \%fs_prefs; + } elsif ($1 eq "stats") { + $hash = \%fs_stats; + } elsif ($1 =~ /queue (.*)$/) { + while (!defined $fs_queues[$1]) { + push (@fs_queues, { %fs_queue_defaults }); + @{$fs_queues[$#fs_queues]->{queue}} = (); + } + $hash = $fs_queues[$1]; + } else { + print_msg("Unknown config section: $_"); + $hash = \%garbage; + } + next; + } + my ($entry, $value) = split('=', $_, 2); + if (defined $hash->{$entry}) { + $hash->{$entry} = $value; + } else { + print_msg("unknown entry: $_"); + } + } + + close(FILE); + return 0; +} + + +############################################################################### +# save_queue(): saves the current sends & queue to file +############################################################################### +sub save_queue +{ + my $f = $fs_prefs{queuefile}; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + + if (!open(FILE, ">", $f)) { + print_msg("Unable to open $f for writing!"); + return 1; + } + + print (FILE "[QueueFileVersion 1.0]\n"); + + # save the sends (for resuming) + foreach my $slot (0 .. $#fs_sends) { + foreach (sort keys %{$fs_sends[$slot]}) { + next if ($_ eq "dontwarn"); + next if ($_ eq "transfd"); + if ($_ eq "warns") { + print(FILE "$_=>0\0"); + } else { + print(FILE "$_=>$fs_sends[$slot]->{$_}\0"); + } + } + print(FILE "\n"); + } + + # save the queues + foreach (0 .. $#fs_queues) { + my $fsq = $fs_queues[$_]->{queue}; + foreach my $slot (0 .. $#{$fsq}) { + foreach (sort keys %{${$fsq}[$slot]}) { + next if ($_ eq "dontwarn"); + next if ($_ eq "transfd"); + if ($_ eq "warns") { + print(FILE "$_=>0\0"); + } else { + print(FILE "$_=>${$fsq}[$slot]->{$_}\0"); + } + } + print(FILE "\n"); + } + } + + close(FILE); + return 0; +} + +############################################################################### +# load_queue(): (re)loads the queue from file +############################################################################### +sub load_queue +{ + my $f = $fs_prefs{queuefile}; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + + if (!open(FILE, "<", $f)) { + print_msg("Unable to open $f for reading!"); + return 1; + } + + my $queue_version = <FILE>; + chomp $queue_version; + if ($queue_version !~ /^\[QueueFileVersion 1\.[0-9]+]$/) { + print_msg("Queue file format not recognized!"); + print_msg("FServe 2.0 and newer won't work with queue file"); + print_msg(" created by earlier versions on FServe."); + return 1; + } + + if (!@fs_queues) { + # create a very first queue :) + push (@fs_queues, { %fs_queue_defaults }); + @{$fs_queues[$#fs_queues]->{queue}} = (); + } + + # empty all queues + foreach (0 .. $#fs_queues) { + @{$fs_queues[$_]->{queue}} = (); + } + + while (<FILE>) { + s/\n//g; + my %rec = (); + my $ignore = 0; + + foreach my $line (split("\0", $_)) { + my ($entry, $value) = split('=>', $line, 2); + $rec{$entry} = $value; + } +# print_debug("Read: $rec{nick}|$rec{server_tag}|$rec{file}|$rec{queue}"); + + # don't put it in queue if it is sending + foreach (0 .. $#fs_sends) { +# print_debug("Checking if it's not in fs_sends with: $fs_sends[$_]->{nick}|$fs_sends[$_]->{server_tag}|$fs_sends[$_]->{file}|$fs_sends[$_]->{queue}"); + if ($rec{nick} eq $fs_sends[$_]->{nick} && + $rec{file} eq $fs_sends[$_]->{file} && + $rec{queue} eq $fs_sends[$_]->{queue} && + $rec{server_tag} eq $fs_sends[$_]->{server_tag}) { + $ignore = 1; + } + } + + if (!$ignore) { + # check if it's sending already but isn't in %fs_sends + foreach (Irssi::Irc::dccs()) { +# print_debug("Checking if it's not sending with: $_->{nick}|$_->{servertag}|$_->{arg}"); + if ($_->{type} eq 'SEND' && $_->{nick} eq $rec{nick} && + $_->{arg} eq $rec{file} && + $rec{server_tag} eq $_->{servertag}) { + print_debug("send of '$rec{file}' for $rec{nick}\@$rec{server_tag} was lost, adding to fs_sends"); + push(@fs_sends, { %rec }); + $ignore = 1; + last; + } + } + } + if (!$ignore) { + my $fsq; + if (defined $rec{queue}) { + if (!defined $fs_queues[$rec{queue}]) { + print_msg("unknown queue #$rec{queue}"); + next; + } + $fsq = $fs_queues[$rec{queue}]->{queue}; + } else { + $fsq = $fs_queues[0]->{queue}; + } + # add to queue + if ($rec{resends}) { + # count resended files + my $place = 0; + foreach (0 .. $#{$fsq}) { + $place++ if (${$fsq}[$_]->{resends}); + } + splice(@{$fsq}, $place, 0, { %rec }); + } else { + push(@{$fsq}, { %rec }); + } + } + } + + close(FILE); + return 0; +} + +############################################################################### +# print_log(): write line to log file +############################################################################### +sub print_log +{ + my $f = $fs_prefs{log_name}; + $f =~ s/\$IRSSI/Irssi::get_irssi_dir()/e or $f =~ s/~/$ENV{"HOME"}/; + if (!$logfp && $fs_prefs{log_name} && open(LOGFP, ">>", $f)) { + $logfp = \*LOGFP; + select((select($logfp), $|++)[0]); + } + return if !$logfp; + my ($msg) = @_; + $msg =~ s/^\s*|\s*$//gs; + print $logfp localtime()." $msg\n"; +} + +# vim:noexpandtab:ts=4 |