#!/usr/bin/python import socket,os,sys,re,time import atexit,curses,curses.panel,curses.ascii from cStringIO import StringIO import config from area import area HELP='''\ F2 - Search menu | Keys in many windows: s - search for a large film | Cursor keys - move cursor f - filter searches | Enter - start an download l - search for small file | or hub connection 1 - sort by GDL count | i - show more info 2 - sort by size | r, ^R - redraw window F3 - GDL menu - download status | /, c - enter DCTC command D - delete this download | q - QUIT this program F4 - Server list menu | d, D - stop connection to this server | F5 - This help screen | F6 - Unused GLD list (attachable) | F7 - Chat window | F8 - Error list window | ''' # curses functions class nc_class: def __init__(self): # init curses atexit.register(curses.endwin) self.win=curses.initscr() self.win.clear() curses.noecho() self.win.keypad(1) self.maxy,self.maxx=self.win.getmaxyx() # define colors curses.start_color() curses.use_default_colors() curses.init_pair(1,curses.COLOR_WHITE,curses.COLOR_BLUE) self.BLUE=curses.color_pair(1) curses.init_pair(2,curses.COLOR_BLACK,curses.COLOR_CYAN) self.CYAN=curses.color_pair(2) curses.init_pair(3,curses.COLOR_BLACK,curses.COLOR_GREEN) self.GREEN=curses.color_pair(3) curses.init_pair(4,curses.COLOR_BLACK,curses.COLOR_WHITE) self.WHITE=curses.color_pair(4) curses.init_pair(5,curses.COLOR_WHITE,curses.COLOR_BLACK) self.BLACK=curses.color_pair(5) curses.init_pair(6,curses.COLOR_BLACK,curses.COLOR_YELLOW) self.YELLOW=curses.color_pair(6) def newbox(self,name,l=-1,c=-1,y=0,x=0,color=0): if l<0: l=nc.maxy-7 if c<0: c=nc.maxx w=curses.newwin(l,c,y,x) w.bkgd(' ',color) w.border() w.addstr(0,5,'[ %s ]' % name) dw=w.subpad(l-2,c-3,y+1,x+2) dw.scrollok(1) return curses.panel.new_panel(w),dw,color,l-2,name def getkey(self): r=self.win.getch() try: return chr(r) except: return r # init windows nc=nc_class() class dcwin: def __init__(self,win): self.panel,self.win,self.color,size,name=win self.size=size-1 self.name=name self.clear() def clear(self): self.win.bkgdset(' ',self.color) self.win.erase() def echo(self,txt,add='\n',attr=curses.A_NORMAL): self.win.addstr(add+txt,attr) # command processors def kilomega(n,e='B',d=1024.0,max=9999.0): mb,me='',' ' if n>max: n=n/d mb,me='K','' if n>max: n=n/d mb='M' return "%4.1f %s%s%s" % (n,mb,e,me) def totime(t): return "%d:%02d:%02d" % (t/60/60,(t/60)%60,t%60) def printable(txt): return ''.join([{True:x,False:'_'}[curses.ascii.isprint(x)] for x in txt]) def getproc(procs,name): for proc in procs: if proc.name==name: return proc return None def search_pattern(what,filename): for s in what.split('$'): if not (s.lower() in filename.lower()): return False return True class processor: name='unknown' cur_pos=0 cur_skip=0 menu=[] next_show=0 def __init__(self,win,dcc=None): self.win=win self.dcc=dcc def process(self,mtype,ins,hubname): pass def show(self): return None def autoshow(self): if time.time()>self.next_show: self.show() def cursor(self,y=0,pg=0): self.cur_skip+=pg self.cur_pos+=y if self.cur_pos<0: self.cur_pos=0 self.cur_skip-=1 elif self.cur_pos>=len(self.menu): self.cur_pos=len(self.menu)-1 elif self.cur_pos>=self.win.size: self.cur_pos=self.win.size-1 self.cur_skip+=1 if self.cur_skip<0: if pg!=0: self.cur_pos=0 self.cur_skip=0 if self.cur_skip+self.win.size+1>len(self.menu): if pg!=0: self.cur_pos=self.win.size-1 self.cur_skip=len(self.menu)-self.win.size if self.cur_skip<0: self.cur_skip=0 if self.cur_pos>len(self.menu): self.cur_pos=len(self.menu)-1 #scr_debug.echo("%d %d" % (self.cur_skip,self.cur_pos)) self.show() class ignore(processor): def process(self,mtype,ins,hubname): if mtype in ['VAR','INFO','DEBUG','ASTRT','RFRSH']: return 1 return 0 class undefined(processor): def process(self,mtype,ins,hubname): self.win.echo("UNKNOWN: %s %s" % (mtype,ins)) return 1 class help(processor): name='help' def __init__(self,win,dcc=None): for l in HELP.split('\n'): win.echo(l) class chat(processor): name='chat' def process(self,mtype,ins,hubname): if mtype=='CHAT': self.win.echo(ins.rstrip("|").replace('\r','\n')) return 1 elif mtype in ('EXFER','BXFER'): self.win.echo('%s: %s' % (mtype,ins)) return 0 class search(processor): name='search' def __init__(self,win,dcc,what,ftype=1,size=0,sizetype='L'): if size>0: self.search_for='/SRCH *%s*%d*%s*%d' % (what,ftype,sizetype,size) else: self.search_for='/SRCH *%s*%d' % (what,ftype) dcc.cmd(self.search_for) self.results={} self.t0=time.time() self.win=win self.next_show=time.time()+1 self.cur_pos=0 self.what=what self.dcc=dcc self.cmp_fx=self.cmp_count def process(self,mtype,ins,hubname): if mtype!='SREST': return 0 try: nickname,filename,filesize,slots,hubname,ip,null=ins.split('|') except ValueError: self.win.echo("ERR: %s" % ins) return 0 free,slots=[int(x) for x in slots.split('/')] key=(int(filesize),hubname) if self.results.has_key(key): self.results[key].append((nickname,filename,free,slots,ip)) else: self.results[key]=[(nickname,filename,free,slots,ip)] return 1 def cmp(self,la,lb): if la/dev/null 2>&1 &') if os.fork()==0: [os.close(x) for x in range(3)] [os.open('/dev/null',os.O_RDONLY) for x in range(3)] os.execvp(config.dctc_exec[0],config.dctc_exec+[hub]) def enter(self,key): cmd=self.menu[self.cur_skip+self.cur_pos][4] hub=self.menu[self.cur_skip+self.cur_pos][1] scr_debug.echo("%s: %s '%s'" % (cmd,hub,self.getkey(self.dcc.hubstatus,hub))) if cmd=='/START': # start all clients for hub in [x[1] for x in self.menu if x[1]]: if '-----' in hub: continue self.start_client(hub) elif cmd=='/HUBLIST': # reload hublist import hublist self.hubs=sorted(hublist.get(), key=lambda x:int(x[3]), reverse=True) elif cmd and cmd[0]=='/': self.dcc.cmd_all(cmd) if cmd=='/FORCEQUIT': scr_debug.echo("Removing ~/.dctc/running content") running_dir=os.path.join(os.environ['HOME'],".dctc/running") for file in os.listdir(running_dir): try: os.unlink(os.path.join(running_dir, file)) except: pass elif self.getkey(self.dcc.hubstatus,hub) in ['*','e']: # kill this client scr_debug.echo('Stopping client %s' % hub) for ts,surl,hubname in self.dcc.ts: if hubname==hub: if key in ['\n','d','D']: dctc.cmd('/QUIT',ts,surl) else: dctc.cmd('/FORCEQUIT',ts,surl) else: # start client scr_debug.echo('Starting client %s [%s:%s]' \ % (hub,self.getkey(self.dcc.hubstatus,hub),hub)) self.start_client(hub) time.sleep(1) self.dcc.__init__() # reload hublist def show(self): #dctc.cmd_all('/HUBNAME') self.next_show=time.time()+5 self.display() def display(self): self.win.clear() lines=['-'*40,'-'*25,'','-'*5,0] basic_menu=[ ['START ALL HUBS IN LIST','','','','/START'], ['STOP ALL HUBS IN LIST','','','','/QUIT'], ['KILL ALL HUBS IN LIST','','','','/FORCEQUIT'], ['RELOAD HUBLIST','','','','/HUBLIST'], lines ] self.active_hublist=[x[2] for x in self.dcc.ts] self.menu=basic_menu+config.hubs+self.hubs+[lines] for hub in self.active_hublist: #if hub not in [x[1] for x in self.menu]: usercount=self.dcc.usercount(hub) self.menu.append([self.getkey(self.dcc.hubnames,hub), hub,'',usercount,'']) line=0 for name,addr,desc,conns,_ in self.menu[self.cur_skip:self.cur_skip+self.win.size]: attr=curses.A_NORMAL mode=' ' if addr in self.dcc.hubstatus.keys(): mode=self.dcc.hubstatus[addr] if mode=='*': attr=nc.GREEN elif mode=='e': attr=nc.YELLOW elif addr in self.active_hublist: attr=nc.GREEN mode='*' if line==self.cur_pos: attr=nc.BLACK line+=1 self.win.echo('%40s %25s %5s %s ' \ % (printable(name[:40]),printable(addr[:25]),conns,mode), attr=attr) # DCTC classes class filetype: any=1 audio=2 compressed=3 document=4 exe=5 picture=6 videos=7 folder=8 reg_reply=re.compile('^([^ \]]+) *\] "(.*)"(.*)\r?\n?$') scr_errors=dcwin(nc.newbox('Errors',color=nc.BLACK)) scr_gdl=dcwin(nc.newbox('GDL\'s',color=nc.GREEN)) scr_gdlu=dcwin(nc.newbox('GDL\'s',color=nc.GREEN)) scr_chat=dcwin(nc.newbox('Chat',color=nc.WHITE)) scr_hublist=dcwin(nc.newbox('Hublist',color=nc.WHITE)) scr_search=dcwin(nc.newbox('Search',color=nc.BLUE)) scr_help=dcwin(nc.newbox('HELP',color=nc.GREEN)) scr_bottom=dcwin(nc.newbox('Other',7,nc.maxx,nc.maxy-7,color=nc.CYAN)) scr_debug=scr_bottom scr_cur=scr_help class dctc_class: reg_dctc_running=re.compile('^.*/dctc-[0-9A-F]{8}-(.*)$') hubstatus={} hubnames={} def __init__(self,init_client=False): self.ts=[] try: for fn in os.listdir(config.dctc_running): r=re.compile('^(dctc-.*)\.udp$').search(fn) if r: surl="%s/%s" % (config.dctc_running,r.group(1)) ts=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) try: ts.connect(surl) self.ts.append((ts,surl,self.findurl(surl))) if 'dummy_client' in surl: self.master=ts except socket.error,(ec,es): if ec==111: # Connection refused os.unlink(surl) # send default params if init_client: for param in config.conn_params: self.cmd_all(param) except OSError: pass # dctc not started yet def usercount(self,hub): try: for ts,surl,fsurl in self.ts: if hub==fsurl: return os.stat(surl+'.userinfo')[6]/216-1 except: pass return 0 def findurl(self,url): r=self.reg_dctc_running.search(url) if r: return r.group(1) else: scr_debug.echo(url) return '' def cmd(self,cmd,ts=None,surl='UNKNOWN'): scr_errors.echo(cmd) if not ts: ts=self.master try: ts.settimeout(1.0) ts.sendall(cmd+'\n') except socket.error,(ec,es): scr_debug.echo('DCTC died? %s, %s' % (surl,es)) self.hubstatus[surl]=' ' def cmd_all(self,cmd): for ts,surl,hubname in self.ts: self.cmd(cmd,ts,surl) def reply(self): if not self.ts: time.sleep(0.1) d=StringIO() for ts,surl,hubname in self.ts: try: while True: ts.settimeout(0.01) da=ts.recv(8192) d.write(da) if (not da) or (da[-1]=='\n'): return d.getvalue(),hubname except socket.timeout: pass if d.tell()>0: return d.getvalue(),hubname return '','' logfile=open('logfile','wt') dctc=dctc_class() if dctc.ts: scr_cur=scr_gdl # start processes procs=[ help(scr_help), gdl_show(scr_gdl), gdl_unused(scr_gdlu), chat(scr_chat), hublist(scr_hublist,dctc), ignore(scr_errors), undefined(scr_errors) ] # show some basic info scr_bottom.echo("SAL's Direct Connect Client started, %d connections found" \ % len(dctc.ts)) while True: scr_cur.win.refresh() scr_bottom.win.refresh() scr_cur.panel.window().addstr(0,50,time.strftime("[ %c ]")) curses.panel.update_panels() curses.doupdate() nc.win.timeout(1000) kb=nc.getkey() #print kb,nc.KEY_ESC,time.time(), if kb==-1: try: reply=dctc.reply() except socket.error,(ec,es): scr_debug.echo('socket.error: [%d] %s' % (ec,es)) if not reply: continue for r in reply[0].split('\n'): if not r.rstrip(): continue try: mtype,function,ins=reg_reply.search(r).groups() logfile.write("%s: %s\n" % (mtype,ins)) logfile.flush() for proc in procs: if proc.process(mtype,ins,reply[1])>0: break except AttributeError: scr_bottom.echo("ERROR: %s" % r) elif (kb in ['s','l']) and ('Search' in scr_cur.name): # remove searches for proc in procs: if proc.name=='search': procs.remove(proc) scr_bottom.echo("Search for: ") curses.echo() sf=scr_bottom.win.getstr() curses.noecho() if not sf: continue scr_bottom.echo("Search: %s %f" % (sf,time.time())) if kb=='l': cur_proc=search(scr_search, dctc, sf) else: cur_proc=search(scr_search, dctc, sf, size=50*1024*1024) procs.insert(0, cur_proc) elif kb=='1': cur_proc.cmp_fx=cur_proc.cmp_count cur_proc.show() elif kb=='2': cur_proc.cmp_fx=cur_proc.cmp_size cur_proc.show() elif kb in ['d','D','\n','x','f','R','i']: cur_proc.enter(kb) elif kb in ['r',curses.KEY_REFRESH,'\x0c']: cur_proc.show() elif kb==curses.KEY_DOWN: cur_proc.cursor(1) elif kb==curses.KEY_UP: cur_proc.cursor(-1) elif kb==curses.KEY_NPAGE: cur_proc.cursor(0,13) elif kb==curses.KEY_PPAGE: cur_proc.cursor(0,-13) elif kb==curses.KEY_HOME: cur_proc.cursor(0,-99999) elif kb==curses.KEY_END: cur_proc.cursor(0,99999) elif kb==curses.KEY_F2: scr_cur=scr_search scr_cur.panel.top() cur_proc=getproc(procs,'search') elif kb==curses.KEY_F3: scr_cur=scr_gdl cur_proc=getproc(procs,'gdl_show') cur_proc.show() scr_cur.panel.top() elif kb==curses.KEY_F4: scr_cur=scr_hublist scr_cur.panel.top() cur_proc=getproc(procs,'hublist') dctc.__init__() elif kb==curses.KEY_F5: scr_cur=scr_help scr_cur.panel.top() cur_proc=getproc(procs,'help') elif kb==curses.KEY_F6: scr_cur=scr_gdlu cur_proc=getproc(procs,'gdl_unused') cur_proc.show() scr_cur.panel.top() elif kb==curses.KEY_F7: scr_cur=scr_chat scr_cur.panel.top() cur_proc=getproc(procs,'chat') elif kb==curses.KEY_F8: scr_cur=scr_errors scr_cur.panel.top() elif kb in ('/','c'): scr_bottom.echo("Enter dctc command: ") curses.echo() cmd=scr_bottom.win.getstr() curses.noecho() dctc.cmd_all('/%s' % cmd) elif kb=='t': scr_bottom.echo("test %f" % time.time()) cur_proc=testproc(scr_search, dctc) procs.insert(0, cur_proc) elif kb in ['q','\x1b']: sys.exit(0) elif kb!=-1: scr_bottom.echo('Unknown key pressed: %s' % repr(kb)) for proc in procs: proc.autoshow()