- 3413F8F586BCF01D2C230B537F987EB38C2C25F1BD133875128B441B1746C5901E8D573215169B22391F24BA71F1B3858ECB50C40C1010D4C4A1F07F56682711
+ F79F7C4C71AD9151C80FEED185ACBF54B531EAAFE10DB7A1A3A1E559176EA8C821114AD03DF77CEBCD4AF83EE3E64C2ADB6A2EEC52B8E152DC8A0926B37F59B9
logotron/reader.py
(7 . 20)(7 . 17)
482 psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
483 psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
484 import time
485 import datetime
486 from datetime import timedelta
487 from datetime import timedelta, datetime
488 import sys
489 reload(sys)
490 sys.setdefaultencoding('utf8')
491 import os
492 import threading
493 import re
494 from datetime import datetime
495 from urlparse import urljoin
496 from flask import Flask, request, session, url_for, redirect, Response, \
497 render_template, abort, g, flash, _app_ctx_stack, make_response, \
498 jsonify
499 from flask import Flask
500 ##############################################################################
501
502 ##############################################################################
(49 . 6)(46 . 8)
504 DB_DEBUG = int(cfg.get("db", "db_debug"))
505 # Logism:
506 Base_URL = cfg.get("logotron", "base_url")
507 App_Root = cfg.get("logotron", "app_root")
508 CSS_File = cfg.get("logotron", "css_file")
509 Era = int(cfg.get("logotron", "era"))
510 DEBUG = int(cfg.get("logotron", "www_dbg"))
511 Max_Raw_Ln = int(cfg.get("logotron", "max_raw"))
(126 . 13)(125 . 13)
513 ## Get base URL
514 def get_base():
515 if DEBUG:
516 return request.host_url
517 return Base_URL
518
519 return request.host_url.rstrip('/')
520 return Base_URL.rstrip('/')
521
522 # Get perma-URL corresponding to given log line
523 def line_url(l):
524 return "{0}log/{1}/{2}#{3}".format(get_base(),
525 return "{0}{1}{2}/{3}#{4}".format(get_base(),
526 App_Root,
527 l['chan'],
528 l['t'].strftime(Date_Short_Format),
529 l['idx'])
(141 . 28)(140 . 24)
531 # Get current time
532 now = datetime.now()
533 # Data for channel display :
534 chan_tbl = {}
535 chan_list = []
536 chan_idx = 0
537 for chan in Channels:
538 chan_tbl[chan] = {}
539 chan_tbl[chan]['show'] = False
540
541 chan_formed = chan
542 if chan == selected_chan:
543 chan_formed = "<span class='highlight'>" + chan + "</span>"
544
545 chan_tbl[chan]['link'] = """<a href="{0}log/{1}"><b>{2}</b></a>""".format(
546 get_base(), chan, chan_formed)
547
548 last_time = query_db(
549 '''select t, idx from loglines where chan=%s
550 and idx = (select max(idx) from loglines where chan=%s) ;''',
551 [chan, chan], one=True)
552
553 last_time_txt = ""
554 time_field = ""
555 last_time_url = ""
556 if last_time != None:
557 span = (now - last_time['t'])
558 days = span.days
559
560 # Only add to the list if it should be visible, otherwise continue
561 if days > Days_Hide and chan != selected_chan and not show_all_chans:
562 continue
563
564 hours = span.seconds/3600
565 minutes = (span.seconds%3600)/60
566
(172 . 36)(167 . 24)
568 last_time_txt += '%dh ' % hours
569 if minutes != 0:
570 last_time_txt += '%dm' % minutes
571
572 time_field = """<i><a href="{0}log/{1}/{2}#{3}">{4}</a></i>""".format(
573
574 last_time_url = "{0}{1}{2}/{3}#{4}".format(
575 get_base(),
576 App_Root,
577 chan,
578 last_time['t'].strftime(Date_Short_Format),
579 last_time['idx'],
580 last_time_txt)
581 last_time['idx'])
582
583 if (days <= Days_Hide) or (chan == selected_chan) or show_all_chans:
584 chan_tbl[chan]['show'] = True
585 chan_list.append({ 'name': chan })
586
587 chan_tbl[chan]['time'] = time_field
588
589 ## Generate channel selector bar :
590 s = """<table align="center" class="chantable"><tr>"""
591 for chan in Channels:
592 if chan_tbl[chan]['show']:
593 s += """<th>{0}</th>""".format(chan_tbl[chan]['link'])
594 s += "</tr><tr>"
595 ## Generate last-activ. links for above :
596 for chan in Channels:
597 if chan_tbl[chan]['show']:
598 s += """<td>{0}</td>""".format(chan_tbl[chan]['time'])
599 # wrap up:
600 s += "</tr></table>"
601 return s
602 chan_list[chan_idx]['last_time_url'] = last_time_url
603 chan_list[chan_idx]['last_time_txt'] = last_time_txt
604 chan_list[chan_idx]['chan_url'] = "{0}{1}{2}{3}".format(
605 get_base(), App_Root, chan, '/' if chan == Default_Chan else '')
606
607 chan_idx += 1
608
609 # Make above callable from inside htm templater:
610 app.jinja_env.globals.update(gen_chanlist=gen_chanlist)
611 return chan_list
612
613
614 # HTML Tag Regex
(257 . 7)(240 . 7)
616 ## Format given log line for display
617 def format_logline(l, highlights = [], select=[], showchan=False):
618 payload = html_escape(l['payload'])
619
620
621 # Format ordinary links:
622 payload = re.sub(stdlinks_re,
623 r'<a href="\1" target=\'_blank\'>\1</a>', payload)
(265 . 20)(248 . 20)
625 # Now also format [link][text] links :
626 payload = re.sub(boxlinks_re,
627 r'<a href="\1" target=\'_blank\'>\2</a>', payload)
628
629
630 # For ancient logs strictly: substitute orig. link with our logger :
631 if l['era'] < 3:
632 payload = re.sub(era1_re,
633 r'<a href="/ilog/trilema/\2', payload)
634
635
636 # Adjust era 2 links in all cases:
637 payload = re.sub(era2_re,
638 r'<a href="/ilog/trilema/\2', payload)
639
640
641 # If this is a search result, illuminate the matched strings:
642 if highlights != []:
643 payload = highlight_text(highlights, payload)
644
645
646 bot = ""
647 if l['speaker'] in Bots:
648 bot = " bot"
(297 . 15)(280 . 15)
650
651 if showchan:
652 speaker = '<small>(' + l['chan'] + ')</small> ' + speaker
653
654
655 # If 'action', annotate:
656 if l['self']:
657 separator = ""
658 payload = "<i>" + payload + "</i>"
659 speaker = "<i>" + speaker + "</i>"
660
661
662 # HTMLize the given line :
663 s = ("<div id='{0}' class='{6}{5}'>"
664 s = ("<div id='{0}' class='logline {6}{5}'>"
665 "<a class='nick' title='{2}'"
666 " href=\"{3}\">{1}</a>{7} {4}</div>").format(l['idx'],
667 speaker,
(318 . 7)(301 . 7)
669 return s
670
671 # Make above callable from inside htm templater:
672 app.jinja_env.globals.update(format_logline=format_logline)
673 app.jinja_env.globals.update(format_logline=format_logline)
674
675
676 @app.route('/rnd/<chan>')
(335 . 10)(318 . 9)
678 return redirect(line_url(rnd_line))
679
680
681 @app.route('/log/<chan>/<date>')
682 @app.route('/log/<chan>', defaults={'date': None})
683 @app.route('/log/', defaults={'chan': Default_Chan, 'date': None})
684 @app.route('/log', defaults={'chan': Default_Chan, 'date': None})
685 @app.route('%s<chan>/<date>' % App_Root)
686 @app.route('%s<chan>' % App_Root, defaults={'date': None})
687 @app.route('%s' % App_Root, defaults={'chan': Default_Chan, 'date': None})
688 def log(chan, date):
689 # Handle rubbish chan:
690 if chan not in Channels:
(353 . 13)(335 . 13)
692
693 # Get possible 'show all'
694 show_all = request.args.get('all', default = 0, type = int)
695
696
697 # Get current time
698 now = datetime.now()
699
700 # Whether we are viewing 'current' tail
701 tail = False
702
703
704 # If viewing 'current' log:
705 if date == None:
706 date = now.strftime(Date_Short_Format)
(370 . 7)(352 . 7)
708 day_start = datetime.strptime(date, Date_Short_Format)
709 except Exception, e:
710 return redirect(url_for('log'))
711
712
713 # Determine the end of the interval being shown
714 day_end = day_start + timedelta(days=1)
715
(383 . 43)(365 . 51)
717 '''select * from loglines where chan=%s
718 and t between %s and %s order by idx asc;''',
719 [chan, day_start, day_end], one=False)
720
721
722 # Optional 'reverse gear' knob:
723 if rev == 1:
724 lines.reverse()
725
726
727 # Generate navbar for the given date:
728 prev_day = ""
729 next_day = ""
730
731
732 prev_t = query_db(
733 '''select t from loglines where chan=%s
734 and t < %s order by idx desc limit 1;''',
735 [chan, day_start], one=True)
736
737
738 if prev_t != None:
739 prev_day = prev_t['t'].strftime(Date_Short_Format)
740
741
742 if not tail:
743 next_t = query_db(
744 '''select t from loglines where chan=%s
745 and t > %s order by idx asc limit 1;''',
746 [chan, day_end], one=True)
747
748
749 if next_t != None:
750 next_day = next_t['t'].strftime(Date_Short_Format)
751
752
753 # Generate url for css file based on config value
754 css_url = url_for('static', filename=CSS_File)
755
756 chan_list = gen_chanlist(chan, show_all)
757
758 # Return the HTMLized text
759 return render_template('log.html',
760 chan = chan,
761 loglines = lines,
762 sel = (sel_start, sel_end),
763 date = date,
764 prev_day = prev_day,
765 next_day = next_day,
766 rev = not rev,
767 show_all = show_all,
768 idle_day = Days_Hide)
769 css_url = css_url,
770 app_root = App_Root,
771 chan = chan,
772 chan_list = chan_list,
773 loglines = lines,
774 sel = (sel_start, sel_end),
775 date = date,
776 prev_day = prev_day,
777 next_day = next_day,
778 rev = not rev,
779 idle_day = Days_Hide,
780 show_all = show_all)
781
782
783 # Primarily for use with 'era 1' and 'era 2' :
(438 . 26)(428 . 26)
785 # If given chan/idx not found:
786 if item == None:
787 return redirect(url_for('log'))
788
789
790 # Determine date where item appears in log :
791 item_date = item['t'].strftime(Date_Short_Format)
792
793
794 # Go there:
795 return redirect("/log/" + chan + "/" + item_date + "#" + idx)
796
797 return redirect(App_Root + chan + "/" + item_date + "#" + idx)
798
799
800 @app.route('/log-raw/<chan>')
801 def rawlog(chan):
802 res = ""
803
804
805 # Handle rubbish chan:
806 if chan not in Channels:
807 return Response("EGGOG: No such Channel!", mimetype='text/plain')
808
809
810 # Get start and end indices:
811 idx_start = request.args.get('istart', default = 0, type = int)
812 idx_end = request.args.get('iend', default = 0, type = int)
813
814
815 # Malformed bounds?
816 if idx_start > idx_end:
817 return Response("EGGOG: Start must precede End!",
(473 . 7)(463 . 7)
819 '''select * from loglines where chan=%s
820 and idx between %s and %s order by idx asc;''',
821 [chan, idx_start, idx_end], one=False)
822
823
824 # Retrieve raw lines in classical Phf format:
825 for l in lines:
826 action = ""
(486 . 7)(476 . 7)
828 action,
829 speaker,
830 l['payload'])
831
832
833 # Return plain text:
834 return Response(res, mimetype='text/plain')
835
(507 . 7)(497 . 11)
837 chan = request.args.get('chan', default = Default_Chan, type = str)
838 query = request.args.get('q', default = '', type = str)
839 offset = request.args.get('after', default = 0, type = int)
840
841 show_all = 0
842
843 if len(query) < Min_Query_Length:
844 return redirect(url_for('log'))
845
846 # channels to search in
847 chans = []
848
(519 . 6)(513 . 7)
850 chans = Channels
851 legend = "<i>all logged channels</i>"
852 showchan = True
853 show_all = 1
854 else:
855 # Handle possible rubbish chan:
856 if chan not in Channels:
(527 . 7)(522 . 7)
858 # search in selected channel only
859 chans = [chan]
860 legend = chan
861
862
863 nres = 0
864 searchres = []
865 tokens_orig = []
(545 . 11)(540 . 11)
867 from_users.append(t.split(':')[1]) # Record user for 'from' query
868 else:
869 tokens_standard.append(t)
870
871
872 from_users = ['%' + sanitize_speaker(t) + '%' for t in from_users]
873 tokens_orig = [re_escape(t) for t in tokens_standard]
874 tokens_formed = ['%' + t + '%' for t in tokens_orig]
875
876
877 # Query is usable; perform the search on DB and get the finds
878 if from_users == []:
879 searchres = query_db(
(571 . 23)(566 . 29)
881 tokens_formed,
882 Max_Search_Results,
883 offset], one=False)
884
885
886 # Number of search results returned in this query
887 nres = len(searchres)
888
889 # Whether to display 'back' button :
890 back = (offset != 0)
891
892
893 # Whether to display 'forward' button :
894 forw = (nres == Max_Search_Results)
895
896
897 # Starting index of search results
898 sres = offset
899
900
901 # Ending index of search results
902 eres = offset + min(nres, Max_Search_Results)
903
904
905 # Generate url for css file based on config value
906 css_url = url_for('static', filename=CSS_File)
907
908 chan_list = gen_chanlist(chan, show_all)
909
910 return render_template('searchres.html',
911 css_url = css_url,
912 query = query,
913 hquery = html_escape(query),
914 legend = legend,
(597 . 9)(598 . 11)
916 forw = forw,
917 psize = Max_Search_Results,
918 chan = chan,
919 chan_list = chan_list,
920 tokens = tokens_orig,
921 loglines = searchres,
922 showchan = showchan)
923 showchan = showchan,
924 show_all = show_all)
925
926 # Comment this out if you don't have one
927 @app.route('/favicon.ico')