AdaCode / logdb.xml

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<!-- Saved on Monday, December 03, 2012, 12:00 PM -->
<!-- MuClient version 4.77 -->

<!-- Plugin "LogDB" generated by Plugin Wizard -->

<muclient>
<plugin
   name="LogDB"
   author="Ada"
   id="fbaf2e815e7e425cb6df1be0"
   language="Python"
   purpose="Log Database"
   date_written="2012-12-05 12:00:00"
   requires="4.62"
   version="1.3"
   >
<description trim="y">
<![CDATA[
Org logs in Achaea poof after 6 days, this plugin reads and maintains a database of these logs on your computer so they stay with you as long as you want. You can search through these logs using keywords and date ranges as well. Have a look at the available aliases to interact with it. I'll add the ability to paginate search results in later versions.

This plugin is written in Python, please see this page to setup Python on your computer: https://bitbucket.org/adayoung/adacode/wiki/Python/SQLite%20support%20in%20MUSHClient

The following aliases are available:

    LogDB:readlog <org> [line to begin at] [how many days back]
    LogDB:search <org> <searchtext> [how many days back]
    LogDB:search <org> <searchtext> [from:yyyy-mm-dd] [to:yyyy-mm-dd]

The plugin will ask you for a file location on first load, just point it wherever you want your database file to be created. If you get "OperationalError: database is locked" error while trying to add an entry, please delete the .journal file in %APPDATA%\MUSHClient to resolve the issue.
]]>
</description>

</plugin>

<!--  Get our standard constants -->

<include name="constants.pys"/>

<!--  Triggers  -->

<triggers>
  <trigger
   enabled="y"
   group="LogDB"
   keep_evaluating="y"
   match="^General log for (.+)\:$"
   name="ReadLogPreamble"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
world.EnableTrigger("PromptCaptureLogDB", True)
world.EnableTrigger("LogCapture", True)
orgname = "%1"
lognotes = []
orgid = ProcessOrg(orgname)
  </send>
  </trigger>
  <trigger
   enabled="n"
   group="LogDB"
   keep_evaluating="y"
   match="^(\d+/\d+/\d+ \d+\:\d+\:\d+) \- (.+)$"
   name="LogCapture"
   regexp="y"
   send_to="12"
   sequence="100"
  >
  <send>
timestamp = "%1".replace("/", "-")
lognotes.append("%s - %s" % (timestamp, "%2"))
  </send>
  </trigger>
  <trigger
   group="LogDB"
   enabled="n"
   keep_evaluating="y"
   match="^\d+h(, \d+m)?(, \d+e)?(, \d+w)? [@cexkdb]*(?: Vote)?-"
   name="PromptCaptureLogDB"
   regexp="y"
   send_to="12"
   sequence="900"
  >
  <send>
world.EnableTrigger("LogCapture", False)
world.EnableTrigger("PromptCaptureLogDB", False)
ProcessLogs(orgname, lognotes, orgid)
  </send>
  </trigger>
</triggers>

<!--  Aliases  -->

<aliases>
  <alias
   match="^LogDB:readlog (.+?)( \d+)?( \d+)?$"
   enabled="y"
   regexp="y"
   send_to="12"
   ignore_case="y"
   sequence="100"
  >
  <send>
if len("%2") > 0 and len("%3") > 0:
    ShowLog("%1", int("%2"), int("%3"))
elif len("%2") > 0:
    ShowLog("%1", int("%2"))
else:
    ShowLog("%1")
  </send>
  </alias>
  <alias
   match="^LogDB:search (\w+) (.+?)( \d+)?$"
   enabled="y"
   regexp="y"
   send_to="12"
   ignore_case="y"
   sequence="100"
  >
  <send>
if len("%3") > 0:
    SearchLogByDay("%1", "%2", int("%3"))
else:
    SearchLogByDay("%1", "%2", 0)
  </send>
  </alias>
  <alias
   match="^LogDB:search (\w+) (.+?) from\:(.+?) to\:(.+?)$"
   enabled="y"
   regexp="y"
   send_to="12"
   ignore_case="y"
   sequence="100"
  >
  <send>
SearchLog("%1", "%2", "%3", "%4")
  </send>
  </alias>
  <alias
   match="^LogDB:test (.*?)$"
   enabled="y"
   regexp="y"
   send_to="12"
   ignore_case="y"
   sequence="100"
  >
  <send>exec("%1")</send>
  </alias>
</aliases>

<!--  Script  -->

<script>
<![CDATA[
import re
import sqlite3
from datetime import datetime, timedelta

import Tkinter
root = Tkinter.Tk()
root.withdraw() # to hide the Tk GUI window as per http://stackoverflow.com/a/1796726

from tkFileDialog import asksaveasfilename, askopenfilename

def Note(message): # not writing world.Note everywhere ^_^
    world.Note(message)

DBFile = world.GetVariable("dbpath")
if not DBFile:
    while not (DBFile and len(DBFile) > 0):
        DBFile = asksaveasfilename(defaultextension=".db", filetypes=[("SQLite3 database file", "*.db")], title="Specify the location of your new database.")
    world.SetVariable("dbpath", DBFile)
    world.SaveState

db = sqlite3.connect(DBFile)
cursor = db.cursor()

def createdb():
    cursor.execute("PRAGMA foreign_keys=ON")
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS orgs (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        orgname TEXT UNIQUE
    )""")
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS org_logs (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT,
        message TEXT,
        orgid INTEGER REFERENCES orgs(id) ON DELETE CASCADE
    )""")
    cursor.execute("""
    CREATE INDEX IF NOT EXISTS logs_index ON org_logs (orgid, timestamp, message)
    """)
    db.commit()

createdb()

def ProcessOrg(orgname):
    cursor.execute("SELECT id FROM orgs WHERE orgname=?", (orgname, ))
    results = cursor.fetchall()
    if not len(results) > 0:
        cursor.execute("INSERT INTO orgs (orgname) VALUES (?)", (orgname, ))
        cursor.execute("SELECT last_insert_rowid()")
        results = cursor.fetchone()
        return results[0]
    else:
        return results[0][0]

def ProcessLogs(orgname, lognotes, orgid):
    import_map = []
    for i in lognotes:
        splitlog = i.split('-')
        timestamp = "-".join(splitlog[:3])
        message = " ".join(splitlog[3:])[1:]
        cursor.execute("SELECT * FROM org_logs l join orgs o on l.orgid=o.id WHERE o.orgname=? AND l.timestamp=datetime(?) AND l.message=?", (orgname, timestamp, message))
        results = cursor.fetchall()
        if not len(results) > 0:
            import_map.append((orgid, timestamp, message))
    cursor.executemany("INSERT INTO org_logs (orgid, timestamp, message) VALUES (?, datetime(?), ?)", import_map)
    db.commit()

def ValidateOrgname(orgname):
    cursor.execute("SELECT orgname FROM orgs WHERE orgname LIKE ?", ("%" + orgname + "%", ))
    orgnames = cursor.fetchall()
    if len(orgnames) > 1:
        Note("Oops, multiple organizations match the supplied input:")
        for i in orgnames:
            Note(i[0])
    elif len(orgnames) < 1:
        Note("No matching organisations found :(")
    else:
        orgname = orgnames[0][0]
        return orgname
    return None

def ShowLog(orgname, startfrom=None, fromdays=None):
    if startfrom is None:
        startfrom = 0
    if fromdays is None:
        fromdays = 0

    orgname = ValidateOrgname(orgname)
    if orgname is None: # bail out
        return

    datenow = datetime.now().date()
    datefrom = datenow - timedelta(days=fromdays)
    dateto = datefrom + timedelta(days=1)
    datefrom = datefrom.strftime("%Y-%m-%d %H:%M:%S")
    dateto = dateto.strftime("%Y-%m-%d %H:%M:%S")

    cursor.execute("SELECT timestamp, message FROM org_logs l join orgs o on l.orgid=o.id WHERE o.orgname=? AND l.timestamp>=datetime(?) AND l.timestamp<datetime(?) ORDER BY l.timestamp ASC", (orgname, datefrom, dateto))
    results = cursor.fetchall()
    Note("General log for %s:" % orgname)
    for i in results[startfrom:]:
        Note("%s - %s" % (i[0], i[1]))
    if not len(results[startfrom:]) > 0:
        Note("No activity.")

def SearchLogByDay(orgname, searchtext, fromdays=None):
    if fromdays is None:
        fromdays = 0

    datenow = datetime.now().date()
    datefrom = datenow - timedelta(days=fromdays)
    datefrom = datefrom.strftime("%Y-%m-%d")

    SearchLog(orgname, searchtext, datefrom, datefrom)

def SearchLog(orgname, searchtext, fromdate=None, todate=None):
    datenow = datetime.now().date()
    if fromdate is None:
        fromdate = datenow.strftime("%Y-%m-%d")
    if todate is None:
        todate = datenow.strftime("%Y-%m-%d")

    orgname = ValidateOrgname(orgname)
    if orgname is None: # bail out
        return

    # validate date format
    if not re.match("^\d{4}\-\d{2}\-\d{2}$", fromdate):
        Note("The supplied date is invalid. The format is YYYY-MM-DD.")
        return
    if not re.match("^\d{4}\-\d{2}\-\d{2}$", todate):
        Note("The supplied date is invalid. The format is YYYY-MM-DD.")
        return

    todate = todate + " 23:59:59" # just to include this day as well

    cursor.execute("SELECT timestamp, message FROM org_logs l join orgs o on l.orgid=o.id WHERE o.orgname=? AND l.timestamp>=datetime(?) AND l.timestamp<=datetime(?) AND l.message LIKE ? ORDER BY l.timestamp ASC", (orgname, fromdate, todate, "%" + searchtext + "%"))
    results = cursor.fetchall()
    Note("Searching [%s] logs for [%s] in the range [%s] to [%s]" % (orgname, searchtext, fromdate, todate[:-9]))
    if len(results) > 0:
        for i in results:
            Note("%s - %s" % (i[0], i[1]))
        Note("Total matches: %d" % len(results))
    else:
        Note("Your query did not return any results.")
]]>
</script>

<!--  Plugin help  -->

<aliases>
  <alias
   script="OnHelp"
   match="LogDB:help"
   ignore_case="y"
   enabled="y"
  >
  </alias>
</aliases>

<script>
<![CDATA[
def OnHelp (a, b, c):
    world.note (world.GetPluginInfo (world.GetPluginID, 3))
]]>
</script>

</muclient>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.