Advertisement
mkv

logcast

mkv
Feb 10th, 2014
246
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.40 KB | None | 0 0
  1. #!/usr/bin/env python
  2. """logcast.py: Logfile Broadcaster"""
  3. __version__   = "1.77"
  4. __author__    = "ed <irc.rizon.net>"
  5. __credits__   = ["Shiz", "Vin", "Wessie", "stackoverflow.com"]
  6. __license__   = "BSD"
  7. __copyright__ = 2014
  8.  
  9. import sys
  10. import os
  11. import time
  12. import collections
  13. import asyncore
  14. import socket
  15. import signal
  16. import threading
  17.  
  18. PASSWORD = 'hinamizawa'
  19. ENABLE_AUTH = False
  20. MSG_LEN = 8192
  21.  
  22.  
  23.  
  24. def colorgrid(up, second):
  25.     dark = '\033[38;5;232m'
  26.     lite = '\033[38;5;255m'
  27.     if up == 'c':
  28.         rest = '\033[22;39m'
  29.         if second is not None:
  30.             if second > 7:
  31.                 rest = '\033[0;48;5;%sm' % second
  32.             else:
  33.                 rest = '\033[0;4%sm' % second
  34.         t = 3
  35.     else:
  36.         t = 4
  37.         if second is not None:
  38.             if second > 7:
  39.                 lite = '\033[22;38;5;%sm' % second
  40.             else:
  41.                 lite = '\033[22;3%sm' % second
  42.             dark = lite
  43.         rest = '\033[0m'
  44.     msg = rest + '\033[J'
  45.  
  46.     msg = '%s\n  bright base, lowercase c   ' % msg
  47.     for m in range(0, 2):
  48.         for n in range(0, 8):
  49.             msg += '\033[1;%d%dm%2d ' % (m + 3, n, n)
  50.         msg += rest + ' '
  51.         if up == 'c': break
  52.         msg += lite
  53.  
  54.     msg = '%s\n%s  normal base, uppercase C   ' % (msg, rest)
  55.     for m in range(0, 2):
  56.         for n in range(0, 8):
  57.             msg += '\033[%d%dm%2d ' % (m + 3, n, n)
  58.         msg += rest + ' '
  59.         if up == 'c': break
  60.         msg += lite
  61.  
  62.     msg += '\n\n  '
  63.     for y in range(0, 12):
  64.         fg = dark if y>1 and y<10 else lite
  65.         for x in range(0, 18):
  66.            
  67.             cell =  3 if x>11 else \
  68.                     2 if x> 5 else 1
  69.            
  70.             v = 0x94+x if cell == 3 else \
  71.                 0x52+x if cell == 2 else \
  72.                 0x10+x
  73.            
  74.             v = v +  0 + 6 * y if y < 6 else \
  75.                 v + 30 + 6 * (12-y)
  76.            
  77.             p = '%s  ' % rest if x == 5 or x == 11 else ''
  78.            
  79.             if   cell == 1: fg = dark if 2<y< 9 and (y != 8  or x> 0) else lite
  80.             elif cell == 2: fg = dark if 1<y<11 and (y != 10 or x> 6) else lite
  81.             elif cell == 3: fg = dark if   y> 0 and (y != 11 or x>12) else lite
  82.             if   up == 'c': fg = ''
  83.            
  84.             msg = '%s%s\033[%d8;5;%dm %3d%s' % (msg, fg, t, v, v, p)
  85.        
  86.         msg += rest + '\n  '
  87.  
  88.     msg += '\n\n   ' + lite
  89.     for n in range(232, 256):
  90.         v = n if n < 244 else 256 - (n-243)
  91.         msg = '%s\033[%d8;5;%dm  %d ' % (msg, t, v, v)
  92.         if n == 243:
  93.             msg += rest + '\n\n   '
  94.             if up == 'C': msg += dark
  95.     msg += rest + '\n'
  96.     return msg
  97.  
  98.  
  99.  
  100. class Printer:
  101.    
  102.     def __init__(self):
  103.         self.mutex = threading.Lock()
  104.    
  105.     def p(self, data, usercount=None):
  106.         with self.mutex:
  107.             if len(data) < 13:
  108.                 data += ' ' * 13
  109.             if usercount:
  110.                 sys.stdout.write('%s\n     %d users\r' % (data, usercount))
  111.             else:
  112.                 sys.stdout.write('%s\n' % (data,))
  113.             sys.stdout.flush()
  114.  
  115. def fmt():
  116.     return time.strftime('%d/%m/%Y, %H:%M:%S')
  117.  
  118. def num(c):
  119.     try:
  120.         return int(c)
  121.     except:
  122.         return None
  123.  
  124.  
  125.  
  126. class RemoteClient(asyncore.dispatcher):
  127.    
  128.     def __init__(self, host, socket, address):
  129.         asyncore.dispatcher.__init__(self, socket)
  130.         self.host = host
  131.         self.socket = socket
  132.         self.authed = not ENABLE_AUTH
  133.         self.outbox = collections.deque()
  134.         self.replies = collections.deque()
  135.         self.input_mode = None
  136.         self.full_path = False
  137.         self.enable_titles = True
  138.         self.title = None
  139.         self.fg = None
  140.         self.bg = None
  141.    
  142.     def say(self, message):
  143.         if self.input_mode is None:
  144.             self.outbox.append(message)
  145.    
  146.     def reply(self, message):
  147.         self.replies.append(message.replace('\r', '').replace('\n', '\r\n'))
  148.    
  149.     def set_title(self, title):
  150.         self.title = title
  151.         if self.enable_titles:
  152.             self.send_title()
  153.    
  154.     """ if title not None:  send title without saving """
  155.     def send_title(self, title=None):
  156.         if title != None or self.enable_titles:
  157.             if title is None:
  158.                 title = self.title
  159.                 if title is None:
  160.                     title = '(file not found)'
  161.                
  162.                 # default for STDIN:  entire cmdline
  163.                 # default for paths:  just filename
  164.                 if self.full_path == host.stdin:
  165.                     if host.stdin:
  166.                         offset = title.find(' ')
  167.                         title = title[:offset]
  168.                     else:
  169.                         title = title.replace('\\', '/')
  170.                         offset = title.rfind('/')
  171.                         title = title[offset+1:]
  172.            
  173.             title = title.replace('\r','').replace('\n','')
  174.             self.outbox.appendleft("\033]0;%s\007" % (title,))
  175.             #print "Set title to <%s>" % (self.title,)
  176.    
  177.     def handle_read(self):
  178.         data = self.recv(MSG_LEN)
  179.         if not data:
  180.             self.host.part(self)
  181.         elif not self.authed:
  182.             if data[:len(PASSWORD)] == PASSWORD:
  183.                 host.con('@  ', self.addr)
  184.                 self.authed = True
  185.                 self.send_title()
  186.             else:
  187.                 host.con('~  ', self.addr)
  188.         else:
  189.             up = data[:1]
  190.             op = up.lower()
  191.            
  192.             #if len(op) == 1 and ord(op) > 32 and ord(op) < 127:
  193.             #   host.con('%s  ' % (op,), self.addr)
  194.             #else:
  195.             #   host.con('?  ', self.addr)
  196.            
  197.             if op == 'm':
  198.                 op = 'c'
  199.                 data = 'c119,22'
  200.            
  201.             if self.input_mode == 'h':
  202.                 self.input_mode = None
  203.            
  204.             if self.input_mode == 't':
  205.                 self.input_mode = None
  206.                 self.send_title(data)
  207.            
  208.             elif op == 'h':
  209.                 self.input_mode = 'h'
  210.                 self.reply("""
  211.  
  212.       L o g c a s t   M e n u
  213. ==========================================
  214.  [t]  set window Title manually
  215.  [e]  Enable filename as window title
  216.  [d]  Disable all window title changes
  217.  [f]  toggle Full path or just filename
  218.  [c]  set Color (or reset formatting)
  219.  [n]  Newlines, hundreds of them
  220. ------------------------------------------
  221.  c=          print current colours
  222.  c3,1        yellow(3) on red(1)
  223.  c? and C?   colour charts
  224. ------------------------------------------
  225.  Flushing log output...   Press [ENTER]
  226. ------------------------------------------
  227. """) #"""
  228.             elif op == 't':
  229.                 self.input_mode = 't'
  230.                 self.enable_titles = False
  231.                 self.reply("""
  232. input window title:
  233. """) #"""
  234.             elif op == 'e':
  235.                 self.enable_titles = True
  236.                 self.input_mode = None
  237.                 self.send_title()
  238.            
  239.             elif op == 'd':
  240.                 self.enable_titles = False
  241.                 self.input_mode = None
  242.            
  243.             elif op == 'c':
  244.                 self.input_mode = None
  245.                 msg = None
  246.                
  247.                 if len(data) > 1 \
  248.                 and data[1] == '?':
  249.                     self.input_mode = 'h'
  250.                     color = None
  251.                     if len(data) > 2:
  252.                         color = num(data[2:].strip())
  253.                    
  254.                     msg = colorgrid(up, color)
  255.                     self.fg = None
  256.                     if up == 'C':
  257.                         self.bg = None
  258.                    
  259.                     if color is not None:
  260.                         if up == 'C':
  261.                             self.fg = color
  262.                         else:
  263.                             self.bg = color
  264.                
  265.                 elif len(data) > 1 \
  266.                 and data[1] == '=':
  267.                     fg = self.fg if self.fg is not None else 'default'
  268.                     bg = self.bg if self.bg is not None else 'default'
  269.                     msg = '[ color %s on %s ]\n' % (fg, bg)
  270.                
  271.                 elif len(data) > 1:
  272.                     arg = data[1:].strip().partition(',')
  273.                     fg = num(arg[0].strip())
  274.                     bg = num(arg[2].strip())
  275.                    
  276.                     msg = ''
  277.                     li = '22;39'
  278.                     if up == op:
  279.                         li = '1'
  280.                    
  281.                     if fg > 7 or bg > 7:
  282.                         if fg is not None:
  283.                             msg += "\033[0;38;5;%dm" % (fg)
  284.                             self.fg = fg
  285.                            
  286.                         if bg is not None:
  287.                             msg += "\033[48;5;%dm" % (bg)
  288.                             self.bg = bg
  289.                    
  290.                     elif fg is not None or bg is not None:
  291.                         if fg is not None:
  292.                             msg += "\033[%s;3%sm" % (li, fg)
  293.                             self.fg = fg
  294.                            
  295.                         if bg is not None:
  296.                             msg += "\033[4%sm" % (bg)
  297.                             self.bg = bg
  298.                    
  299.                     if len(msg) <3 :
  300.                         msg = None
  301.                
  302.                 if msg is None:
  303.                     msg = "\033[0m"
  304.                     self.fg = None
  305.                     self.bg = None
  306.                
  307.                 self.reply("%s\033[J" % (msg,))
  308.            
  309.             elif op == 'f':
  310.                 self.input_mode = None
  311.                 self.full_path = not self.full_path
  312.                 self.send_title()
  313.            
  314.             elif op == 'n':
  315.                 self.reply('\n' * 100)
  316.            
  317.             else:
  318.                 self.input_mode = None
  319.                 if len(data) > 2:
  320.                     self.reply("""
  321. Unknown command
  322. """) #"""
  323.    
  324.     def writable(self):
  325.         return self.replies or \
  326.             (self.outbox and self.input_mode is None )
  327.    
  328.     def handle_write(self):
  329.         if not self.writable():
  330.             return
  331.        
  332.         box = self.outbox
  333.         if self.replies:
  334.             box = self.replies
  335.        
  336.         message = box.popleft()
  337.         if self.authed:
  338.             sent = self.send(message)
  339.             if sent < len(message):
  340.                 box.appendleft(message[sent:])
  341.  
  342.  
  343.  
  344. class Host(asyncore.dispatcher):
  345.    
  346.     def __init__(self, p, address=('localhost', 0)):
  347.         asyncore.dispatcher.__init__(self)
  348.         self.p = p
  349.         self.stdin = False
  350.         self.filename = None
  351.         self.remote_clients = []
  352.         self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  353.         self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  354.         self.bind(address)
  355.         self.listen(1)
  356.    
  357.     def con(self, msg, adr, cli=None):
  358.         if (cli is None):
  359.             cli = len(self.remote_clients)
  360.         msg = ' %s %s - %s :%s' % (msg, fmt(), adr[0], adr[1])
  361.         self.p.p(msg, cli)
  362.    
  363.     def handle_accept(self):
  364.         socket, addr = self.accept()
  365.         self.con(' ++', addr, len(self.remote_clients) + 1)
  366.         remote = RemoteClient(self, socket, addr)
  367.         self.remote_clients.append(remote)
  368.         remote.set_title(self.filename)
  369.    
  370.     def handle_read(self):
  371.         self.recv(MSG_LEN)
  372.    
  373.     def broadcast(self, message):
  374.         for remote_client in self.remote_clients:
  375.             #if remote_client.input_mode is None:
  376.             remote_client.say(message)
  377.    
  378.     def part(self, remote):
  379.         self.remote_clients.remove(remote)
  380.         self.con('  -', remote.addr)
  381.    
  382.     def set_filename(self, filename):
  383.         self.filename = filename
  384.         for remote_client in self.remote_clients:
  385.             remote_client.set_title(filename)
  386.  
  387.  
  388.  
  389. def signal_handler(signal, frame):
  390.     print '\n-(!) SHUTDOWN-'
  391.     sys.exit(0)
  392.  
  393.  
  394.  
  395. def piper(p, host, path):
  396.    
  397.     provider = None
  398.     try:
  399.         inlet = os.readlink('/proc/%d/fd/0' % os.getpid())
  400.         for proc in os.walk('/proc').next()[1]:
  401.             try:
  402.                 outlet = '/proc/%s/fd/1' % proc
  403.                 outlet = os.readlink(outlet)
  404.                 if inlet == outlet:
  405.                     provider = proc
  406.             except: pass
  407.        
  408.         if provider is not None:
  409.             try:
  410.                 with open('/proc/%s/cmdline' % provider, 'rb') as f:
  411.                     provider = f.read().replace("\000", " ")
  412.             except: pass
  413.    
  414.     except: pass
  415.     if provider is None:
  416.         provider = '(unknown process on STDIN)'
  417.    
  418.     host.stdin = True
  419.     host.set_filename(provider)
  420.     p.p(' --- %s - %s' % (fmt(), provider))
  421.     while True:
  422.         line = sys.stdin.readline()
  423.         if not line: sys.exit(0)
  424.         host.broadcast(line)
  425.  
  426.  
  427.  
  428. def reader(p, host, path):
  429.    
  430.     def get_mtime(file):
  431.         info = os.stat(os.path.join(path, file))
  432.         return info.st_mtime
  433.    
  434.     had_error = False
  435.     is_directory = None
  436.     last_filename = None
  437.     file_position = 0
  438.     time.sleep(0.05)
  439.    
  440.     while True:
  441.         try:
  442.             filename = path
  443.             if is_directory is None:
  444.                 is_directory = os.path.isdir(path)
  445.             if is_directory:
  446.                 filename = max(os.listdir(path), key=get_mtime)
  447.                 filename = os.path.join(path, filename)
  448.            
  449.             filesize = os.path.getsize(filename)
  450.             if filename != last_filename:
  451.                 file_position = 0
  452.                 host.set_filename(filename)
  453.                 if not had_error and last_filename is None:
  454.                     file_position = filesize
  455.                 p.p(' --- %s - %s (%d)' % (fmt(), filename, file_position))
  456.            
  457.             last_filename = filename
  458.             if filesize < file_position:
  459.                 file_position = 0
  460.            
  461.             if filesize > file_position:
  462.                 with open(filename, 'rb') as f:
  463.                     f.seek(file_position)
  464.                     data = f.read()
  465.                     endpos = data.rfind('\n') + 1
  466.                     if endpos > 0:
  467.                         file_position += endpos
  468.                         host.broadcast(data[:endpos])
  469.             time.sleep(0.05)
  470.             had_error = False
  471.        
  472.         except:
  473.             #import traceback
  474.             #traceback.print_exc(file=sys.stdout)
  475.             if not had_error:
  476.                 p.p(' (!) %s - FILE NOT FOUND !' % (fmt(),))
  477.             host.set_filename(None)
  478.             last_filename = None
  479.             is_directory = None
  480.             had_error = True
  481.             time.sleep(0.25)
  482.  
  483. """
  484. rm -rf /dev/shm/logcast; mkdir /dev/shm/logcast; while [ true ]
  485. do for x in {1..3}; do date +%s.%N; sleep 0.5; done; sleep 1.5
  486. done >> /dev/shm/logcast/lipsum
  487. ./logcast.py 43214 /dev/shm/logcast
  488. """
  489. if __name__ == '__main__':
  490.    
  491.     if len(sys.argv) <3:
  492.         print
  493.         print '  Usage:  %s  (port)*  (input)*  [output]  ' % sys.argv[0]
  494.         print '-' * 59
  495.         print '  +  Mandatory   argument 1   IPv4 port number to bind'
  496.         print '  +  Mandatory   argument 2   Log file, log folder, or -'
  497.         print '  -   optional   argument 3   Redirect STDOUT to this path,'
  498.         print '                                making the script silent'
  499.         print
  500.         print '  Crontab example  (all in one line):'
  501.         print '     @reboot /usr/bin/logcast.py 43214'
  502.         print '             /var/log/lighttpd/access.log'
  503.         print '             /var/log/logcast-lighttpd'
  504.         sys.exit(1)
  505.    
  506.     if len(sys.argv) >3:
  507.         outpath = sys.argv[3]
  508.         sys.stdout = open(outpath, 'a')
  509.         print '  *  Output to %s' % outpath
  510.    
  511.     print '  *  Capture ^C'
  512.     signal.signal(signal.SIGINT, signal_handler)
  513.    
  514.     print '  *  Switch printer'
  515.     p = Printer()
  516.    
  517.     p.p('  *  Start server')
  518.     host = Host(p, ('0.0.0.0', int(sys.argv[1])))
  519.    
  520.     target = reader
  521.     if sys.argv[2] == '-':
  522.         target = piper
  523.    
  524.     p.p('  *  Fork reader')
  525.     thr = threading.Thread(target=target, args=(p, host, sys.argv[2]))
  526.     thr.daemon = True
  527.     thr.start()
  528.    
  529.     p.p('  *  Running')
  530.     asyncore.loop(0.05)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement