Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- """logcast.py: Logfile Broadcaster"""
- __version__ = "1.77"
- __author__ = "ed <irc.rizon.net>"
- __credits__ = ["Shiz", "Vin", "Wessie", "stackoverflow.com"]
- __license__ = "BSD"
- __copyright__ = 2014
- import sys
- import os
- import time
- import collections
- import asyncore
- import socket
- import signal
- import threading
- PASSWORD = 'hinamizawa'
- ENABLE_AUTH = False
- MSG_LEN = 8192
- def colorgrid(up, second):
- dark = '\033[38;5;232m'
- lite = '\033[38;5;255m'
- if up == 'c':
- rest = '\033[22;39m'
- if second is not None:
- if second > 7:
- rest = '\033[0;48;5;%sm' % second
- else:
- rest = '\033[0;4%sm' % second
- t = 3
- else:
- t = 4
- if second is not None:
- if second > 7:
- lite = '\033[22;38;5;%sm' % second
- else:
- lite = '\033[22;3%sm' % second
- dark = lite
- rest = '\033[0m'
- msg = rest + '\033[J'
- msg = '%s\n bright base, lowercase c ' % msg
- for m in range(0, 2):
- for n in range(0, 8):
- msg += '\033[1;%d%dm%2d ' % (m + 3, n, n)
- msg += rest + ' '
- if up == 'c': break
- msg += lite
- msg = '%s\n%s normal base, uppercase C ' % (msg, rest)
- for m in range(0, 2):
- for n in range(0, 8):
- msg += '\033[%d%dm%2d ' % (m + 3, n, n)
- msg += rest + ' '
- if up == 'c': break
- msg += lite
- msg += '\n\n '
- for y in range(0, 12):
- fg = dark if y>1 and y<10 else lite
- for x in range(0, 18):
- cell = 3 if x>11 else \
- 2 if x> 5 else 1
- v = 0x94+x if cell == 3 else \
- 0x52+x if cell == 2 else \
- 0x10+x
- v = v + 0 + 6 * y if y < 6 else \
- v + 30 + 6 * (12-y)
- p = '%s ' % rest if x == 5 or x == 11 else ''
- if cell == 1: fg = dark if 2<y< 9 and (y != 8 or x> 0) else lite
- elif cell == 2: fg = dark if 1<y<11 and (y != 10 or x> 6) else lite
- elif cell == 3: fg = dark if y> 0 and (y != 11 or x>12) else lite
- if up == 'c': fg = ''
- msg = '%s%s\033[%d8;5;%dm %3d%s' % (msg, fg, t, v, v, p)
- msg += rest + '\n '
- msg += '\n\n ' + lite
- for n in range(232, 256):
- v = n if n < 244 else 256 - (n-243)
- msg = '%s\033[%d8;5;%dm %d ' % (msg, t, v, v)
- if n == 243:
- msg += rest + '\n\n '
- if up == 'C': msg += dark
- msg += rest + '\n'
- return msg
- class Printer:
- def __init__(self):
- self.mutex = threading.Lock()
- def p(self, data, usercount=None):
- with self.mutex:
- if len(data) < 13:
- data += ' ' * 13
- if usercount:
- sys.stdout.write('%s\n %d users\r' % (data, usercount))
- else:
- sys.stdout.write('%s\n' % (data,))
- sys.stdout.flush()
- def fmt():
- return time.strftime('%d/%m/%Y, %H:%M:%S')
- def num(c):
- try:
- return int(c)
- except:
- return None
- class RemoteClient(asyncore.dispatcher):
- def __init__(self, host, socket, address):
- asyncore.dispatcher.__init__(self, socket)
- self.host = host
- self.socket = socket
- self.authed = not ENABLE_AUTH
- self.outbox = collections.deque()
- self.replies = collections.deque()
- self.input_mode = None
- self.full_path = False
- self.enable_titles = True
- self.title = None
- self.fg = None
- self.bg = None
- def say(self, message):
- if self.input_mode is None:
- self.outbox.append(message)
- def reply(self, message):
- self.replies.append(message.replace('\r', '').replace('\n', '\r\n'))
- def set_title(self, title):
- self.title = title
- if self.enable_titles:
- self.send_title()
- """ if title not None: send title without saving """
- def send_title(self, title=None):
- if title != None or self.enable_titles:
- if title is None:
- title = self.title
- if title is None:
- title = '(file not found)'
- # default for STDIN: entire cmdline
- # default for paths: just filename
- if self.full_path == host.stdin:
- if host.stdin:
- offset = title.find(' ')
- title = title[:offset]
- else:
- title = title.replace('\\', '/')
- offset = title.rfind('/')
- title = title[offset+1:]
- title = title.replace('\r','').replace('\n','')
- self.outbox.appendleft("\033]0;%s\007" % (title,))
- #print "Set title to <%s>" % (self.title,)
- def handle_read(self):
- data = self.recv(MSG_LEN)
- if not data:
- self.host.part(self)
- elif not self.authed:
- if data[:len(PASSWORD)] == PASSWORD:
- host.con('@ ', self.addr)
- self.authed = True
- self.send_title()
- else:
- host.con('~ ', self.addr)
- else:
- up = data[:1]
- op = up.lower()
- #if len(op) == 1 and ord(op) > 32 and ord(op) < 127:
- # host.con('%s ' % (op,), self.addr)
- #else:
- # host.con('? ', self.addr)
- if op == 'm':
- op = 'c'
- data = 'c119,22'
- if self.input_mode == 'h':
- self.input_mode = None
- if self.input_mode == 't':
- self.input_mode = None
- self.send_title(data)
- elif op == 'h':
- self.input_mode = 'h'
- self.reply("""
- L o g c a s t M e n u
- ==========================================
- [t] set window Title manually
- [e] Enable filename as window title
- [d] Disable all window title changes
- [f] toggle Full path or just filename
- [c] set Color (or reset formatting)
- [n] Newlines, hundreds of them
- ------------------------------------------
- c= print current colours
- c3,1 yellow(3) on red(1)
- c? and C? colour charts
- ------------------------------------------
- Flushing log output... Press [ENTER]
- ------------------------------------------
- """) #"""
- elif op == 't':
- self.input_mode = 't'
- self.enable_titles = False
- self.reply("""
- input window title:
- """) #"""
- elif op == 'e':
- self.enable_titles = True
- self.input_mode = None
- self.send_title()
- elif op == 'd':
- self.enable_titles = False
- self.input_mode = None
- elif op == 'c':
- self.input_mode = None
- msg = None
- if len(data) > 1 \
- and data[1] == '?':
- self.input_mode = 'h'
- color = None
- if len(data) > 2:
- color = num(data[2:].strip())
- msg = colorgrid(up, color)
- self.fg = None
- if up == 'C':
- self.bg = None
- if color is not None:
- if up == 'C':
- self.fg = color
- else:
- self.bg = color
- elif len(data) > 1 \
- and data[1] == '=':
- fg = self.fg if self.fg is not None else 'default'
- bg = self.bg if self.bg is not None else 'default'
- msg = '[ color %s on %s ]\n' % (fg, bg)
- elif len(data) > 1:
- arg = data[1:].strip().partition(',')
- fg = num(arg[0].strip())
- bg = num(arg[2].strip())
- msg = ''
- li = '22;39'
- if up == op:
- li = '1'
- if fg > 7 or bg > 7:
- if fg is not None:
- msg += "\033[0;38;5;%dm" % (fg)
- self.fg = fg
- if bg is not None:
- msg += "\033[48;5;%dm" % (bg)
- self.bg = bg
- elif fg is not None or bg is not None:
- if fg is not None:
- msg += "\033[%s;3%sm" % (li, fg)
- self.fg = fg
- if bg is not None:
- msg += "\033[4%sm" % (bg)
- self.bg = bg
- if len(msg) <3 :
- msg = None
- if msg is None:
- msg = "\033[0m"
- self.fg = None
- self.bg = None
- self.reply("%s\033[J" % (msg,))
- elif op == 'f':
- self.input_mode = None
- self.full_path = not self.full_path
- self.send_title()
- elif op == 'n':
- self.reply('\n' * 100)
- else:
- self.input_mode = None
- if len(data) > 2:
- self.reply("""
- Unknown command
- """) #"""
- def writable(self):
- return self.replies or \
- (self.outbox and self.input_mode is None )
- def handle_write(self):
- if not self.writable():
- return
- box = self.outbox
- if self.replies:
- box = self.replies
- message = box.popleft()
- if self.authed:
- sent = self.send(message)
- if sent < len(message):
- box.appendleft(message[sent:])
- class Host(asyncore.dispatcher):
- def __init__(self, p, address=('localhost', 0)):
- asyncore.dispatcher.__init__(self)
- self.p = p
- self.stdin = False
- self.filename = None
- self.remote_clients = []
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.bind(address)
- self.listen(1)
- def con(self, msg, adr, cli=None):
- if (cli is None):
- cli = len(self.remote_clients)
- msg = ' %s %s - %s :%s' % (msg, fmt(), adr[0], adr[1])
- self.p.p(msg, cli)
- def handle_accept(self):
- socket, addr = self.accept()
- self.con(' ++', addr, len(self.remote_clients) + 1)
- remote = RemoteClient(self, socket, addr)
- self.remote_clients.append(remote)
- remote.set_title(self.filename)
- def handle_read(self):
- self.recv(MSG_LEN)
- def broadcast(self, message):
- for remote_client in self.remote_clients:
- #if remote_client.input_mode is None:
- remote_client.say(message)
- def part(self, remote):
- self.remote_clients.remove(remote)
- self.con(' -', remote.addr)
- def set_filename(self, filename):
- self.filename = filename
- for remote_client in self.remote_clients:
- remote_client.set_title(filename)
- def signal_handler(signal, frame):
- print '\n-(!) SHUTDOWN-'
- sys.exit(0)
- def piper(p, host, path):
- provider = None
- try:
- inlet = os.readlink('/proc/%d/fd/0' % os.getpid())
- for proc in os.walk('/proc').next()[1]:
- try:
- outlet = '/proc/%s/fd/1' % proc
- outlet = os.readlink(outlet)
- if inlet == outlet:
- provider = proc
- except: pass
- if provider is not None:
- try:
- with open('/proc/%s/cmdline' % provider, 'rb') as f:
- provider = f.read().replace("\000", " ")
- except: pass
- except: pass
- if provider is None:
- provider = '(unknown process on STDIN)'
- host.stdin = True
- host.set_filename(provider)
- p.p(' --- %s - %s' % (fmt(), provider))
- while True:
- line = sys.stdin.readline()
- if not line: sys.exit(0)
- host.broadcast(line)
- def reader(p, host, path):
- def get_mtime(file):
- info = os.stat(os.path.join(path, file))
- return info.st_mtime
- had_error = False
- is_directory = None
- last_filename = None
- file_position = 0
- time.sleep(0.05)
- while True:
- try:
- filename = path
- if is_directory is None:
- is_directory = os.path.isdir(path)
- if is_directory:
- filename = max(os.listdir(path), key=get_mtime)
- filename = os.path.join(path, filename)
- filesize = os.path.getsize(filename)
- if filename != last_filename:
- file_position = 0
- host.set_filename(filename)
- if not had_error and last_filename is None:
- file_position = filesize
- p.p(' --- %s - %s (%d)' % (fmt(), filename, file_position))
- last_filename = filename
- if filesize < file_position:
- file_position = 0
- if filesize > file_position:
- with open(filename, 'rb') as f:
- f.seek(file_position)
- data = f.read()
- endpos = data.rfind('\n') + 1
- if endpos > 0:
- file_position += endpos
- host.broadcast(data[:endpos])
- time.sleep(0.05)
- had_error = False
- except:
- #import traceback
- #traceback.print_exc(file=sys.stdout)
- if not had_error:
- p.p(' (!) %s - FILE NOT FOUND !' % (fmt(),))
- host.set_filename(None)
- last_filename = None
- is_directory = None
- had_error = True
- time.sleep(0.25)
- """
- rm -rf /dev/shm/logcast; mkdir /dev/shm/logcast; while [ true ]
- do for x in {1..3}; do date +%s.%N; sleep 0.5; done; sleep 1.5
- done >> /dev/shm/logcast/lipsum
- ./logcast.py 43214 /dev/shm/logcast
- """
- if __name__ == '__main__':
- if len(sys.argv) <3:
- print
- print ' Usage: %s (port)* (input)* [output] ' % sys.argv[0]
- print '-' * 59
- print ' + Mandatory argument 1 IPv4 port number to bind'
- print ' + Mandatory argument 2 Log file, log folder, or -'
- print ' - optional argument 3 Redirect STDOUT to this path,'
- print ' making the script silent'
- print
- print ' Crontab example (all in one line):'
- print ' @reboot /usr/bin/logcast.py 43214'
- print ' /var/log/lighttpd/access.log'
- print ' /var/log/logcast-lighttpd'
- sys.exit(1)
- if len(sys.argv) >3:
- outpath = sys.argv[3]
- sys.stdout = open(outpath, 'a')
- print ' * Output to %s' % outpath
- print ' * Capture ^C'
- signal.signal(signal.SIGINT, signal_handler)
- print ' * Switch printer'
- p = Printer()
- p.p(' * Start server')
- host = Host(p, ('0.0.0.0', int(sys.argv[1])))
- target = reader
- if sys.argv[2] == '-':
- target = piper
- p.p(' * Fork reader')
- thr = threading.Thread(target=target, args=(p, host, sys.argv[2]))
- thr.daemon = True
- thr.start()
- p.p(' * Running')
- asyncore.loop(0.05)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement