root/xchat/dcclog.py

Revision 24, 9.4 kB (checked in by jmoiron, 2 years ago)
  • fixed a small error in the default log
  • added $S to the docs and comments
Line 
1 #!/usr/bin/env python
2
3 # written by jmoiron, jmoiron.net
4 # licensed under the GNU GPL v2
5 # a copy of the license can be found:
6 #   http://www.gnu.org/licenses/gpl.txt
7
8 """
9 Keeps a log of DCC transfer events.  This is not meant for
10 use with a bot, so it only keeps track of DCC RECV events.
11 """
12
13 import xchat
14 import sys, os
15 import ConfigParser
16 import datetime
17 #import re
18 from exceptions import ValueError
19
20 __module_name__ = "dcclog"
21 __module_version__ = "0.1"
22 __module_description__ = "dcc log"
23
24 # to enable some debugging output, set to True
25 __debugging__ = False
26
27 if __debugging__:
28     import traceback
29
30 # we're going to latch onto the context we're loaded from
31 CONTEXT = xchat.get_context()
32
33 def print_debug(string):
34     global __debugging__
35     if __debugging__:
36         string = str(string)
37         CONTEXT.prnt("\00302" + string + "\003")
38
39 def print_error(string):
40     string = str(string)
41     CONTEXT.prnt("\00304" + string + "\003")
42
43 def print_help(string):
44     string = str(string)
45     CONTEXT.prnt("\00302" + string + "\003")
46
47 def print_success(string):
48     string = str(string)
49     CONTEXT.prnt("\00303" + string + "\003")
50
51 def echo(string):
52     string = str(string)
53     CONTEXT.prnt("\00300" + string + "\003")
54
55 print_info = print_success
56
57 XCHAT_DIR = xchat.get_info("xchatdir")
58 if not XCHAT_DIR.endswith("/"): XCHAT_DIR += "/"
59 XCHAT_DIRLIST = os.listdir(XCHAT_DIR)
60
61 # logfile format:
62 # $D = Date (2006.10.25)
63 # $T = Time (24hr)
64 # $t = Time (12hr)
65 # $N = Nick of sender
66 # $F = filename
67 # $C = CPS
68 # $R = bytes received (pos)
69 # $Z = filesize
70 # $U = resume
71 # %S = status
72
73 CONFIG_FILE = XCHAT_DIR + "jonaspy.conf"
74 CONFIG_FORMAT_OPTIONS = ['D', 'T', 't', 'N', 'F', 'C', 'R', 'Z', 'U', 'S']
75 CONFIG_DEFAULT = {
76     "logformat" : "$D $T :$S: $N \"$F\" [$R/$Z @ $C from byte $U]",
77     "logfile" : XCHAT_DIR + "dcclog.txt",
78 }
79
80 DCC_HOOKS = {}
81 DCC_RECV_EVENTS = [
82     "DCC RECV Abort",
83     "DCC RECV Complete",
84     "DCC RECV Failed",
85     "DCC Stall",
86     "DCC Timeout",
87 ]
88
89 cfg = ConfigParser.SafeConfigParser()
90
91 # cfg stuff throws lots of exceptions
92 def set_cfg_defaults():
93     global cfg, CONFIG_DEFAULT
94     try:
95         cfg.add_section('dcclog')
96         for name, value in CONFIG_DEFAULT.items():
97             cfg.set('dcclog', name, value)
98     except:
99         if __debugging__: traceback.print_exc(sys.stdout)
100
101 def save_cfg(filename):
102     global cfg
103     cfgFile = open(filename, 'w')
104     cfg.write(cfgFile)
105     cfgFile.close()
106
107 # we try to read the config file
108 try:
109     cfgFile = open(CONFIG_FILE)
110     cfg.readfp(cfgFile)
111     cfgFile.close()
112     # if the config doesn't have 'dcclog'
113     if not cfg.has_section('dcclog'):
114         set_cfg_defaults()
115         save_cfg(CONFIG_FILE)
116 # if it doesn't exist, we make one from defaults
117 except:
118     set_cfg_defaults()
119     save_cfg(CONFIG_FILE)
120
121 # TODO: we should make sure our configuration options actually exist
122 # inside the configuration file, and write the defaults if they dont
123 # right now we're just testing for section existence
124
125 # by now the cfg has the right configs
126 cmd_help = {}
127 gen_help = [
128     "\002commands:\002\n",
129     "  \037no options\037         - shows configuration options\n",
130     "  \037[help, ?]\037 <cmd>    - prints this help or detailed help for 'cmd'\n",
131     "  \037set\037 [opt] [value]  - sets configuration option 'opt' to 'value'\n",
132     "\002extra help options:\002\n",
133     "  \037logformat\037          - detailed help on'logformat' option\n",
134 ]
135 cmd_help['msg'] = "   " + "   ".join(gen_help)
136 cmd_help['help'] = """\n\002/dcclog\002 \037[help, ?]\037 <cmd>\n   \00303If <cmd> is provided, it prints detailed help on the command 'cmd'.  If not, it prints the general help for dcclog.\00303"""
137 cmd_help['set'] = """\n\002/dcclog\002 \037[set]\037 [opt] [value]\n   \00303Sets the configuration setting "opt" to "value".  Take care not to set it to options that will break the logger, such as a logformat string that is invalid or a logfile in a location you do not write access to.\00303"""
138 cmd_help['logformat'] = """\n\002logformat special characters\003\n    \00303The log format can be changed to whatever you want, on the fly.  A number of special interpolation sequences are available and begin with '$' followed by a special letter.  The character '$' is not illegal outside of these sequences, but it will be stripped and using it is NOT recommended.  No escaping has been implemented at this time.  The sequences are:\00303
139     $D = Date (2006.10.25)
140     $T = Time (24hr)
141     $t = Time (12hr)
142     $N = Nick of sender
143     $F = filename
144     $C = CPS
145     $R = bytes received
146     $Z = filesize
147     $U = resume point (0 if not resumed)
148         $S = dcc status
149 """
150
151
152 class Entry:
153     def __init__(self, dcc_obj):
154         # datetime.datetime(2006, 10, 14, 16, 44, 52, 660814)
155         self.dcc_obj = dcc_obj
156         self.datetime = datetime.datetime.now()
157         self.date_ts = self.datetime.strftime('%Y.%m.%d')
158         self.time_24 = self.datetime.strftime('%H:%M:%S')
159         self.time_12 = self.datetime.strftime('%I:%M:%S %P')
160         self.nick = str(dcc_obj.nick)
161         self.filename = str(dcc_obj.file)
162         self.cps = str(dcc_obj.cps/1024) + 'KB/sec'
163         self.received = "%.2F" % (dcc_obj.pos/1048576.0) + 'MB'
164         self.filesize = "%.2F" % (dcc_obj.size/1048576.0) + 'MB'
165         self.resume = str(dcc_obj.resume)
166         self.status = ['QUEUED', 'ACTIVE', 'FAILED  ', 'COMPLETE', 'CONNECTED', 'ABORTED '][int(dcc_obj.status)]
167         self.format_options = {
168             'D' : self.date_ts,
169             'T' : self.time_24,
170             't' : self.time_12,
171             'N' : self.nick,
172             'F' : self.filename,
173             'C' : self.cps,
174             'R' : self.received,
175             'Z' : self.filesize,
176             'U' : self.resume,
177             'S' : self.status,
178         }
179
180     # return a string according to our log format
181     def _createLine(self):
182         global cfg, CONFIG_FORMAT_OPTIONS
183         global print_debug
184         format = cfg.get('dcclog', 'logformat').split('$')
185         s = ''
186         print_debug(str(format))
187         for piece in format:
188             if not piece: continue
189             if self.format_options.has_key(piece[0]):
190                 s += self.format_options[piece[0]] + piece[1:]
191         return s
192
193     def commit(self, filename):
194         global print_debug
195         line = self._createLine()
196         print_debug(line)
197         f = open(filename, 'a')
198         f.write(line + '\n')
199         f.close()
200
201 # xchat.nickcmp works in a c-like, bad fashion
202 def sameNick(n1, n2):
203     if not xchat.nickcmp(n1, n2): return True
204     return False
205
206 def getDccObject(botname, filename):
207     l = xchat.get_list('dcc')
208     for item in l:
209         if sameNick(item.nick, botname) and item.file == filename:
210             return item
211
212 def dccRecvComplete(split, full):
213     global cfg
214     # split: filename, filepath, botname, CPS
215     dcc_obj = getDccObject(split[2], split[0])
216     print_debug(dcc_obj)
217     entry = Entry(dcc_obj)
218     entry.commit(cfg.get('dcclog', 'logfile'))
219
220 def dccRecvAbort(split, full):
221     global cfg
222     # split: botname, filename
223     dcc_obj = getDccObject(split[0], split[1])
224     entry = Entry(dcc_obj)
225     entry.commit(cfg.get('dcclog', 'logfile'))
226
227 dccRecvFailed = dccRecvComplete
228 # split: filename, filepath, botname, error
229
230 def dccStall(split, full):
231     # split: dcctype, filename, botname
232     global cfg
233     dcc_obj = getDccObject(split[2], split[1])
234     print_debug(dcc_obj)
235     entry = Entry(dcc_obj)
236     entry.commit(cfg.get('dcclog', 'logfile'))
237
238 dccTimeout = dccStall
239
240 def dispatch(split, full, event):
241     print_debug('Event captured: \"%s\"' % (event))
242     try:
243         {
244             "DCC RECV Complete" : dccRecvComplete,
245             "DCC RECV Abort"    : dccRecvAbort,
246             "DCC RECV Failed"   : dccRecvFailed,
247             "DCC Stall"         : dccStall,
248             "DCC Timeout"       : dccTimeout,
249         }[event](split, full)
250     except:
251         if __debugging__: traceback.print_exc(sys.stdout)
252
253 def showHelp(split, full):
254     # /dcclog help [cmd]
255     global cmd_help
256     if len(split) == 3:
257         if cmd_help.has_key(split[2]):
258             print_help(cmd_help[split[2]])
259             return
260     print cmd_help['msg']
261    
262
263 # TODO: future -- have setting 'logformat' print out a sample
264 def setOption(split, full):
265     # /dcclog set <option> <value>
266     option, value = split[2], split[3]
267     if len(split) != 4:
268         print_error('Error: set takes 2 options.  Make sure you quote arguments that have spaces.')
269         return
270     if option not in CONFIG_DEFAULT.keys():
271         print_error('Error: invalid option.  Valid options are: %s' % (CONFIG_DEFAULT.keys()))
272         return
273     cfg.set('dcclog', option, value)
274     print_info('Option \"%s\" has been set to \"%s\"' % (option, value))
275
276 def showConfiguration():
277     global cfg, CONFIG_DEFAULT
278     print_info('current dcclog configuration:')
279     for item in CONFIG_DEFAULT.keys():
280         val = cfg.get('dcclog', item)
281         print_info(str(item) + ' = \"' + str(val) + '\"')
282
283 def cmdDispatch(split, full, event):
284     print_debug(split)
285     echo('/' + str(full[0]))
286     # /dcclog
287     if len(split) == 1:
288         showConfiguration()
289         return xchat.EAT_XCHAT
290     try:
291         {
292             "help"    : showHelp,
293             "?"       : showHelp,
294             "set"     : setOption,
295         }[split[1]](split, full)
296     except:
297         if __debugging__: traceback.print_exc(sys.stdout)
298     return xchat.EAT_XCHAT       
299
300 for event in DCC_RECV_EVENTS:
301     DCC_HOOKS[event] = xchat.hook_print(event, dispatch, event)
302
303 __unhook__ = xchat.hook_command("dcclog", cmdDispatch, help="/dcclog help for information")
304 print_info("dcclog successfully loaded")
Note: See TracBrowser for help on using the browser.