Deduplicate (de)serialization code shared by UsersDictionary and ChannelsDictionary.

This commit is contained in:
Valentin Lorentz 2019-09-26 22:48:05 +02:00
parent 7d218ec8ce
commit a4f8e3f647
2 changed files with 68 additions and 78 deletions

View File

@ -620,64 +620,27 @@ class DuplicateHostmask(ValueError):
class UsersDictionary(utils.IterableMap): class UsersDictionary(utils.IterableMap):
"""A simple serialized-to-file User Database.""" """A simple serialized-to-file User Database."""
__slots__ = ('noFlush', 'filename', 'users', '_nameCache', __slots__ = ('filename', 'users', '_nameCache', '_hostmaskCache')
'_hostmaskCache')
def __init__(self): def __init__(self):
self.noFlush = False
self.filename = None self.filename = None
self._preserver = unpreserve.Preserver(self.__class__.__name__)
self.users = {} self.users = {}
self.nextId = 0 self.nextId = 0
self._nameCache = utils.structures.CacheDict(1000) self._nameCache = utils.structures.CacheDict(1000)
self._hostmaskCache = utils.structures.CacheDict(1000) self._hostmaskCache = utils.structures.CacheDict(1000)
# This is separate because the Creator has to access our instance.
def open(self, filename): def open(self, filename):
self.filename = filename self.filename = filename
reader = unpreserve.Reader(IrcUserCreator, self) self._preserver.open(filename, IrcUserCreator, self)
try:
self.noFlush = True
try:
reader.readFile(filename)
self.noFlush = False
self.flush()
except EnvironmentError as e:
log.error('Invalid user dictionary file, resetting to empty.')
log.error('Exact error: %s', utils.exnToString(e))
except Exception as e:
log.exception('Exact error:')
finally:
self.noFlush = False
def reload(self):
"""Reloads the database from its file."""
self.nextId = 0
self.users.clear()
self._nameCache.clear()
self._hostmaskCache.clear()
if self.filename is not None:
try:
self.open(self.filename)
except EnvironmentError as e:
log.warning('UsersDictionary.reload failed: %s', e)
else:
log.error('UsersDictionary.reload called with no filename.')
def flush(self): def flush(self):
"""Flushes the database to its file.""" """Flushes the database to its file."""
if not self.noFlush: if self.filename is not None:
if self.filename is not None: L = sorted(self.users.items())
L = list(self.users.items()) blocks = [('user %s' % id, user) for (id, user) in L]
L.sort() self._preserver.flush(self.filename, blocks)
fd = utils.file.AtomicFile(self.filename)
for (id, u) in L:
fd.write('user %s' % id)
fd.write(os.linesep)
u.preserve(fd, indent=' ')
fd.close()
else:
log.error('UsersDictionary.flush called with no filename.')
else: else:
log.debug('Not flushing UsersDictionary because of noFlush.') log.error('UsersDictionary.flush called with no filename.')
def close(self): def close(self):
self.flush() self.flush()
@ -685,6 +648,17 @@ class UsersDictionary(utils.IterableMap):
world.flushers.remove(self.flush) world.flushers.remove(self.flush)
self.users.clear() self.users.clear()
def reload(self):
"""Reloads the database from its file."""
if self.filename is not None:
self.nextId = 0
self.users.clear()
self._nameCache.clear()
self._hostmaskCache.clear()
self.open(self.filename)
else:
log.error('UsersDictionary.reload called with no filename.')
def items(self): def items(self):
return self.users.items() return self.users.items()
@ -832,44 +806,24 @@ class UsersDictionary(utils.IterableMap):
class ChannelsDictionary(utils.IterableMap): class ChannelsDictionary(utils.IterableMap):
__slots__ = ('noFlush', 'filename', 'channels') __slots__ = ('channels', 'filename')
def __init__(self): def __init__(self):
self.noFlush = False
self.filename = None self.filename = None
self._preserver = unpreserve.Preserver(self.__class__.__name__)
self.channels = ircutils.IrcDict() self.channels = ircutils.IrcDict()
def open(self, filename): def open(self, filename):
self.noFlush = True self.filename = filename
try: self._preserver.open(filename, IrcChannelCreator, self)
self.filename = filename
reader = unpreserve.Reader(IrcChannelCreator, self)
try:
reader.readFile(filename)
self.noFlush = False
self.flush()
except EnvironmentError as e:
log.error('Invalid channel database, resetting to empty.')
log.error('Exact error: %s', utils.exnToString(e))
except Exception as e:
log.error('Invalid channel database, resetting to empty.')
log.exception('Exact error:')
finally:
self.noFlush = False
def flush(self): def flush(self):
"""Flushes the channel database to its file.""" """Flushes the channel database to its file."""
if not self.noFlush: if self.filename is not None:
if self.filename is not None: L = sorted(self.channels.items())
fd = utils.file.AtomicFile(self.filename) blocks = [('channel %s' % name, channel) for (name, channel) in L]
for (channel, c) in self.channels.items(): self._preserver.flush(self.filename, blocks)
fd.write('channel %s' % channel)
fd.write(os.linesep)
c.preserve(fd, indent=' ')
fd.close()
else:
log.warning('ChannelsDictionary.flush without self.filename.')
else: else:
log.debug('Not flushing ChannelsDictionary because of noFlush.') log.error('UsersDictionary.flush called with no filename.')
def close(self): def close(self):
self.flush() self.flush()
@ -881,10 +835,7 @@ class ChannelsDictionary(utils.IterableMap):
"""Reloads the channel database from its file.""" """Reloads the channel database from its file."""
if self.filename is not None: if self.filename is not None:
self.channels.clear() self.channels.clear()
try: self.open(self.filename)
self.open(self.filename)
except EnvironmentError as e:
log.warning('ChannelsDictionary.reload failed: %s', e)
else: else:
log.warning('ChannelsDictionary.reload without self.filename.') log.warning('ChannelsDictionary.reload without self.filename.')

View File

@ -27,6 +27,10 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import os
from . import log, utils
class Reader(object): class Reader(object):
"""Opens a file and reads it in blocks, using the `Creator` class to """Opens a file and reads it in blocks, using the `Creator` class to
instantiate an object for each of the blocks. instantiate an object for each of the blocks.
@ -100,5 +104,40 @@ class Reader(object):
self.creator.finish() self.creator.finish()
class Preserver(object):
__slots__ = ('_class_name','noFlush')
def __init__(self, class_name):
self._class_name = class_name
self.noFlush = False
def open(self, filename, Creator, *args, **kwargs):
"""Opens the `filename` and instantiates objects using the provided
`Creator` and args (see the `Reader` class)."""
try:
reader = Reader(Creator, *args, **kwargs)
try:
self.noFlush = True
reader.readFile(filename)
finally:
self.noFlush = False
self.flush()
except (EnvironmentError, Exception) as e:
log.exception('Invalid %s file, resetting to empty.',
self._class_name)
def flush(self, filename, blocks):
if not self.noFlush:
fd = utils.file.AtomicFile(filename)
for (first_line, object_) in blocks:
fd.write(first_line)
fd.write(os.linesep)
object_.preserve(fd, indent=' ')
fd.close()
else:
log.debug('Not flushing %s because of noFlush.',
self._class_name)
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: