|
|
@@ -22,6 +22,8 @@ |
|
|
|
|
|
|
|
from os import expanduser |
|
|
|
import re |
|
|
|
from threading import RLock |
|
|
|
from time import sleep, time |
|
|
|
|
|
|
|
import oursql |
|
|
|
|
|
|
@@ -46,9 +48,13 @@ class DRNClerkBot(Task): |
|
|
|
def setup(self): |
|
|
|
"""Hook called immediately after the task is loaded.""" |
|
|
|
cfg = self.config.tasks.get(self.name, {}) |
|
|
|
|
|
|
|
# Set some wiki-related attributes: |
|
|
|
self.title = cfg.get("page", "Wikipedia:Dispute resolution noticeboard") |
|
|
|
default_summary = "Updating $3 cases for the [[WP:DRN|dispute resolution noticeboard]]." |
|
|
|
self.summary = self.make_summary(cfg.get("summary", default_summary)) |
|
|
|
|
|
|
|
# Templates used in chart generation: |
|
|
|
# Templates used: |
|
|
|
templates = cfg.get("templates", {}) |
|
|
|
self.tl_status = templates.get("status", "DR case status") |
|
|
|
self.tl_notify_party = templates.get("notifyParty", "DRN-notice") |
|
|
@@ -61,16 +67,47 @@ class DRNClerkBot(Task): |
|
|
|
kwargs = cfg.get("sql", {}) |
|
|
|
kwargs["read_default_file"] = expanduser("~/.my.cnf") |
|
|
|
self.conn_data = kwargs |
|
|
|
self.db_access_lock = Lock() |
|
|
|
self.db_access_lock = RLock() |
|
|
|
|
|
|
|
def run(self, **kwargs): |
|
|
|
"""Entry point for a task event.""" |
|
|
|
if not self.db_access_lock.acquire(False): # Non-blocking |
|
|
|
self.logger.info("A job is already ongoing; aborting") |
|
|
|
return |
|
|
|
|
|
|
|
with self.db_access_lock: |
|
|
|
self.logger.info(u"Starting update to [[{0}]]".format(self.title)) |
|
|
|
start = time() |
|
|
|
conn = oursql.connect(**self.conn_data) |
|
|
|
cases = read_database(conn) |
|
|
|
page = self.bot.wiki.get_site().get_page(self.title) |
|
|
|
text = page.get() |
|
|
|
current = read_page(cases, text) |
|
|
|
read_page(conn, cases, text) |
|
|
|
|
|
|
|
# Work! |
|
|
|
# Send messages! |
|
|
|
|
|
|
|
self.save(page, cases) |
|
|
|
|
|
|
|
def save(self, page, cases): |
|
|
|
newtext = text = page.get() |
|
|
|
counter = 0 |
|
|
|
for case in cases: |
|
|
|
if case.old != case.body: |
|
|
|
newtext = newtext.replace(case.old, case.body) |
|
|
|
counter += 1 |
|
|
|
|
|
|
|
worktime = time() - start |
|
|
|
if worktime < 60: |
|
|
|
sleep(60 - worktime) |
|
|
|
page.reload() |
|
|
|
if page.get() != text: |
|
|
|
log = "Someone has edited the page while we were working; restarting" |
|
|
|
self.logger.warn(log) |
|
|
|
return self.run() |
|
|
|
summary = self.summary.replace("$3", str(counter)) |
|
|
|
page.edit(text, summary, minor=False, bot=True) |
|
|
|
self.logger.info(u"Saved page [[{0}]]".format(page.title)) |
|
|
|
|
|
|
|
def read_database(self, conn): |
|
|
|
"""Return a list of _Cases from the database.""" |
|
|
@@ -82,24 +119,42 @@ class DRNClerkBot(Task): |
|
|
|
cases.append(_Case(id_, title, status)) |
|
|
|
return cases |
|
|
|
|
|
|
|
def read_page(self, cases, text): |
|
|
|
def read_page(self, conn, cases, text): |
|
|
|
"""Read the noticeboard content and update the list of _Cases.""" |
|
|
|
tl_status_esc = re.escape(self.tl_status) |
|
|
|
split = re.split("(^==\s*[^=]+?\s*==$)", text, flags=re.M|re.U) |
|
|
|
cases = [] |
|
|
|
case = None |
|
|
|
for item in split: |
|
|
|
if item.startswith("=="): |
|
|
|
if case: |
|
|
|
cases.append(case) |
|
|
|
case = _Case() |
|
|
|
case.title = item[2:-2].strip() |
|
|
|
else: |
|
|
|
templ = re.escape(self.tl_status) |
|
|
|
if case and re.match("\s*\{\{" + templ, item, re.U): |
|
|
|
case.body = case.old_body = item |
|
|
|
case.status = self.read_status(body) |
|
|
|
if case: |
|
|
|
cases.append(case) |
|
|
|
for i in xrange(len(split)): |
|
|
|
if i + 1 == len(split): |
|
|
|
break |
|
|
|
if not split[i].startswith("=="): |
|
|
|
continue |
|
|
|
title = split[i][2:-2].strip() |
|
|
|
body = old = split[i + 1] |
|
|
|
if not re.search("\s*\{\{" + tl_status_esc, body, re.U): |
|
|
|
continue |
|
|
|
status = self.read_status(body) |
|
|
|
re_id = "<!-- EarwigBot Case ID \(please don't modify me\): (.*?) -->" |
|
|
|
try: |
|
|
|
id_ = re.search(re_id, body).group(1) |
|
|
|
case = [case for case in cases if case.id == id_][0] |
|
|
|
except (AttributeError, IndexError): |
|
|
|
id_ = self.select_next_id(conn) |
|
|
|
re_id2 = "(\{\{" + tl_status_esc + "(.*?)\}\})(<!-- Case ID \(please don't modify\): .*? -->)?" |
|
|
|
repl = ur"\1 <!-- Case ID (please don't modify): {0} -->" |
|
|
|
body = re.sub(re_id2, repl.format(id_), body) |
|
|
|
case = _Case(id_, title, status) |
|
|
|
cases.append(case) |
|
|
|
case.body, case.old = body, old |
|
|
|
|
|
|
|
def select_next_id(self, conn): |
|
|
|
"""Return the next incremental ID for a case.""" |
|
|
|
query = "SELECT MAX(case_id) FROM case" |
|
|
|
with conn.cursor() as cursor: |
|
|
|
cursor.execute(query) |
|
|
|
current = cursor.fetchone()[0] |
|
|
|
if current: |
|
|
|
return current + 1 |
|
|
|
return 1 |
|
|
|
|
|
|
|
def read_status(self, body): |
|
|
|
"""Parse the current status from a case body.""" |
|
|
@@ -130,3 +185,4 @@ class _Case(object): |
|
|
|
self.status = status |
|
|
|
|
|
|
|
self.body = None |
|
|
|
self.old = None |