1
0
Fork 0

Introducing data stores

This commit is contained in:
nemunaire 2015-02-10 00:30:04 +01:00
parent e7fd7c5ec4
commit f66d724d40
6 changed files with 181 additions and 72 deletions

View File

@ -24,6 +24,7 @@ import os
import sys
import nemubot
from nemubot import datastore
import nemubot.prompt
from nemubot.prompt.builtins import load_file
from nemubot.prompt.reset import PromptReset
@ -91,8 +92,8 @@ if __name__ == "__main__":
logger.error("%s is not a directory", path)
# Create bot context
context = nemubot.Bot(modules_paths=modules_paths, data_path=args.data_path,
verbosity=args.verbose)
context = nemubot.Bot(modules_paths=modules_paths, data_store=datastore.XML(args.data_path),
verbosity=args.verbose)
if args.no_connect:
context.noautoconnect = True

View File

@ -32,6 +32,7 @@ __version__ = '4.0.dev0'
__author__ = 'nemunaire'
from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
from nemubot import datastore
from nemubot.event import ModuleEvent
from nemubot.hooks.messagehook import MessageHook
from nemubot.hooks.manager import HooksManager
@ -45,13 +46,13 @@ class Bot(threading.Thread):
"""Class containing the bot context and ensuring key goals"""
def __init__(self, ip="127.0.0.1", modules_paths=list(),
data_path=None, verbosity=0):
data_store=datastore.Abstract(), verbosity=0):
"""Initialize the bot context
Keyword arguments:
ip -- The external IP of the bot (default: 127.0.0.1)
modules_paths -- Paths to all directories where looking for module
data_path -- Path to directory where store bot context data
data_store -- An instance of the nemubot datastore for bot's modules
"""
threading.Thread.__init__(self)
@ -65,9 +66,8 @@ class Bot(threading.Thread):
# Context paths
self.modules_paths = modules_paths
self.data_path = None
if data_path is not None:
self.set_data_path(data_path)
self.datastore = data_store
self.datastore.open()
# Keep global context: servers and modules
self.servers = dict()
@ -180,52 +180,6 @@ class Bot(threading.Thread):
logger.exception("Uncatched exception on server read")
# Data path
def set_data_path(self, path):
"""Check if the given path is valid and unlock,
then lock the directory and set the variable
Argument:
path -- the location
"""
lock_file = os.path.join(path, ".used_by_nemubot")
if os.path.isdir(path):
if not os.path.exists(lock_file):
if self.data_path is not None:
self.close_data_path(self.data_path)
self.data_path = path
with open(lock_file, 'w') as lf:
lf.write(str(os.getpid()))
return True
else:
with open(lock_file, 'r') as lf:
pid = lf.readline()
raise Exception("Data dir already locked, by PID %s" % pid)
return False
def close_data_path(self, path=None):
"""Release a locked path
Argument:
path -- the location, self.data_path if None
"""
if path is None:
path = self.data_path
lock_file = os.path.join(path, ".used_by_nemubot")
if os.path.isdir(path) and os.path.exists(lock_file):
os.unlink(lock_file)
return True
return False
# Events methods
def add_event(self, evt, eid=None, module_src=None):
@ -408,6 +362,13 @@ class Bot(threading.Thread):
if module.__name__ in self.modules:
self.unload_module(module.__name__)
# Overwrite print built-in
def prnt(*args):
print("[%s]" % module.__name__, *args)
if hasattr(module, "logger"):
module.logger.info(" ".join(args))
module.print = prnt
self.modules[module.__name__] = module
return True
@ -456,7 +417,7 @@ class Bot(threading.Thread):
def quit(self):
"""Save and unload modules and disconnect servers"""
self.close_data_path()
self.datastore.close()
if self.event_timer is not None:
logger.info("Stop the event timer...")
@ -492,9 +453,9 @@ def hotswap(bak):
bak.stop = True
if bak.event_timer is not None:
bak.event_timer.cancel()
bak.close_data_path()
bak.datastore.close()
new = Bot(str(bak.ip), bak.modules_paths, bak.data_path)
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
new.servers = bak.servers
new.modules = bak.modules
new.modules_configuration = bak.modules_configuration

View File

@ -0,0 +1,18 @@
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2015 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from nemubot.datastore.abstract import Abstract
from nemubot.datastore.xml import XML

View File

@ -0,0 +1,61 @@
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2015 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from nemubot.tools.xmlparser import module_state
class Abstract:
"""Abstract implementation of a module data store, that always return an
empty set"""
def open(self):
return
def close(self):
return
def load(self, module):
"""Load data for the given module
Argument:
module -- the module name of data to load
Return:
The loaded data
"""
return module_state.ModuleState("nemubotstate")
def save(self, module, data):
"""Load data for the given module
Argument:
module -- the module name of data to load
data -- the new data to save
Return:
Saving status
"""
return True
def __enter__(self):
self.open()
return self
def __exit__(self, type, value, traceback):
self.close()

81
nemubot/datastore/xml.py Normal file
View File

@ -0,0 +1,81 @@
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2015 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from nemubot.datastore.abstract import Abstract
from nemubot.tools.xmlparser import parse_file
class XML(Abstract):
"""A concrete implementation of a data store that relies on XML files"""
def __init__(self, basedir):
self.basedir = basedir
def open(self):
"""Lock the directory"""
if os.path.isdir(self.basedir):
lock_file = os.path.join(self.basedir, ".used_by_nemubot")
if not os.path.exists(lock_file):
with open(lock_file, 'w') as lf:
lf.write(str(os.getpid()))
return True
else:
with open(lock_file, 'r') as lf:
pid = lf.readline()
raise Exception("Data dir already locked, by PID %s" % pid)
return False
def close(self):
"""Release a locked path"""
lock_file = os.path.join(self.basedir, ".used_by_nemubot")
if os.path.isdir(self.basedir) and os.path.exists(lock_file):
os.unlink(lock_file)
return True
return False
def _get_data_file_path(self, module):
"""Get the path to the module data file"""
return os.path.join(self.basedir, module + ".xml")
def load(self, module):
"""Load data for the given module
Argument:
module -- the module name of data to load
"""
data_file = self._get_data_file_path(module)
if os.path.isfile(data_file):
return parse_file(data_file)
else:
return Abstract.load(self, module)
def save(self, module, data):
"""Load data for the given module
Argument:
module -- the module name of data to load
data -- the new data to save
"""
return data.save(self._get_data_file_path(module))

View File

@ -86,18 +86,13 @@ class ModuleLoader(SourceFileLoader):
module.__LOADED__ = True
module.logger = logging.getLogger("nemubot.module." + fullname)
def prnt(*args):
print("[%s]" % module.__name__, *args)
module.logger.info(" ".join(args))
def prnt_dbg(*args):
if module.DEBUG:
print("{%s}" % module.__name__, *args)
module.logger.debug(" ".join(args))
def mod_save():
fpath = os.path.join(self.context.data_path, module.__name__ + ".xml")
module.print_debug("Saving DATAS to " + fpath)
module.DATAS.save(fpath)
self.context.datastore.save(module.__name__, module.DATAS)
def send_response(server, res):
if server in self.context.servers:
@ -129,7 +124,6 @@ class ModuleLoader(SourceFileLoader):
module.REGISTERED_HOOKS = list()
module.REGISTERED_EVENTS = list()
module.DEBUG = self.context.verbosity > 0
module.print = prnt
module.print_debug = prnt_dbg
module.send_response = send_response
module.add_hook = add_hook
@ -139,15 +133,8 @@ class ModuleLoader(SourceFileLoader):
module.del_event = del_event
if not hasattr(module, "NODATA"):
module.DATAS = module_state.ModuleState("nemubotstate")
if self.context.data_path is not None:
data_file = os.path.join(self.context.data_path,
module.__name__ + ".xml")
if os.path.isfile(data_file):
module.DATAS = parse_file(data_file)
module.save = mod_save
else:
module.save = lambda: False
module.DATAS = self.context.datastore.load(module.__name__)
module.save = mod_save
else:
module.DATAS = None
module.save = lambda: False