Introducing data stores
This commit is contained in:
parent
e7fd7c5ec4
commit
f66d724d40
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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))
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue