A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

80 lines
3.2 KiB

  1. # -*- coding: utf-8 -*-
  2. # A module to manage bot tasks.
  3. import time
  4. import traceback
  5. import threading
  6. import os
  7. from config import schedule
  8. task_list = dict() # the key is the task's name, the value is the task's class instance
  9. def load_tasks():
  10. """Load all valid task classes from wiki/tasks/, and add them to the task_list."""
  11. files = os.listdir(os.path.join("wiki", "tasks")) # get all files in wiki/tasks/
  12. files.sort() # alphabetically sort list of files
  13. for f in files:
  14. if not os.path.isfile(os.path.join("wiki", "tasks", f)): # ignore non-files
  15. continue
  16. if f.startswith("_") or not f.endswith(".py"): # ignore non-python files or files beginning with "_"
  17. continue
  18. load_class_from_file(f)
  19. print "Found %s tasks: %s." % (len(task_list), ', '.join(task_list.keys()))
  20. def load_class_from_file(f):
  21. """Look in a given file for the task class."""
  22. global task_list
  23. module = f[:-3] # strip .py from end
  24. try:
  25. exec "from wiki.tasks import %s as m" % module
  26. except: # importing the file failed for some reason...
  27. print "Couldn't load task file %s:" % f
  28. traceback.print_exc()
  29. return
  30. try:
  31. task_class = m.Task()
  32. except:
  33. print "Couldn't find or get task class in file %s:" % f
  34. traceback.print_exc()
  35. return
  36. task_name = task_class.task_name
  37. task_list[task_name] = task_class
  38. print "Added task %s from wiki/tasks/%s..." % (task_name, f)
  39. def start_tasks(now=time.gmtime()):
  40. """Start all tasks that are supposed to be run at a given time."""
  41. tasks = schedule.check(now.tm_min, now.tm_hour, now.tm_mday, now.tm_mon, now.tm_wday) # get list of tasks to run this turn
  42. for task in tasks:
  43. if isinstance(task, tuple): # they've specified kwargs, so pass those to start_task
  44. start_task(task[0], **task[1])
  45. else: # otherwise, just pass task_name
  46. start_task(task)
  47. def start_task(task_name, **kwargs):
  48. """Start a given task in a new thread. Pass args to the task's run function."""
  49. print "Starting task '{}' in a new thread...".format(task_name)
  50. try:
  51. task = task_list[task_name] # get the class for this task, a subclass of BaseTask
  52. except KeyError:
  53. print "Couldn't find task '{}': wiki/tasks/{}.py does not exist.".format(task_name, task_name)
  54. return
  55. task_thread = threading.Thread(target=lambda: task_wrapper(task, **kwargs)) # Normally we'd do task_wrapper(task, **kwargs), but because of threading we'd have to do Thread(target=task_wrapper, args=(task, **kwargs)), which doesn't work because the **kwargs is inside a tuple, not inside function params. Use lambda to get around the args=tuple nonsense
  56. task_thread.name = "{} ({})".format(task_name, time.strftime("%b %d %H:%M:%S"))
  57. task_thread.daemon = True # stop bot task threads automagically if the main bot stops
  58. task_thread.start()
  59. def task_wrapper(task, **kwargs):
  60. """Wrapper for task classes: run the task and catch any errors."""
  61. try:
  62. task.run(**kwargs)
  63. except:
  64. print "Task '{}' raised an exception and had to stop:".format(task.task_name)
  65. traceback.print_exc()
  66. else:
  67. print "Task '{}' finished without error.".format(task.task_name)