bazarr/libs/cork/json_backend.py

135 lines
4.3 KiB
Python

# Cork - Authentication module for the Bottle web framework
# Copyright (C) 2013 Federico Ceratto and others, see AUTHORS file.
# Released under LGPLv3+ license, see LICENSE.txt
"""
.. module:: json_backend
:synopsis: JSON file-based storage backend.
"""
from logging import getLogger
import os
import shutil
import sys
try:
import json
except ImportError: # pragma: no cover
import simplejson as json
from .base_backend import BackendIOException
is_py3 = (sys.version_info.major == 3)
log = getLogger(__name__)
try:
dict.iteritems
py23dict = dict
except AttributeError:
class py23dict(dict):
iteritems = dict.items
class BytesEncoder(json.JSONEncoder):
def default(self, obj):
if is_py3 and isinstance(obj, bytes):
return obj.decode()
return json.JSONEncoder.default(self, obj)
class JsonBackend(object):
"""JSON file-based storage backend."""
def __init__(self, directory, users_fname='users',
roles_fname='roles', pending_reg_fname='register', initialize=False):
"""Data storage class. Handles JSON files
:param users_fname: users file name (without .json)
:type users_fname: str.
:param roles_fname: roles file name (without .json)
:type roles_fname: str.
:param pending_reg_fname: pending registrations file name (without .json)
:type pending_reg_fname: str.
:param initialize: create empty JSON files (defaults to False)
:type initialize: bool.
"""
assert directory, "Directory name must be valid"
self._directory = directory
self.users = py23dict()
self._users_fname = users_fname
self.roles = py23dict()
self._roles_fname = roles_fname
self._mtimes = py23dict()
self._pending_reg_fname = pending_reg_fname
self.pending_registrations = py23dict()
if initialize:
self._initialize_storage()
self._refresh() # load users and roles
def _initialize_storage(self):
"""Create empty JSON files"""
self._savejson(self._users_fname, {})
self._savejson(self._roles_fname, {})
self._savejson(self._pending_reg_fname, {})
def _refresh(self):
"""Load users and roles from JSON files, if needed"""
self._loadjson(self._users_fname, self.users)
self._loadjson(self._roles_fname, self.roles)
self._loadjson(self._pending_reg_fname, self.pending_registrations)
def _loadjson(self, fname, dest):
"""Load JSON file located under self._directory, if needed
:param fname: short file name (without path and .json)
:type fname: str.
:param dest: destination
:type dest: dict
"""
try:
fname = "%s/%s.json" % (self._directory, fname)
mtime = os.stat(fname).st_mtime
if self._mtimes.get(fname, 0) == mtime:
# no need to reload the file: the mtime has not been changed
return
with open(fname) as f:
json_data = f.read()
except Exception as e:
raise BackendIOException("Unable to read json file %s: %s" % (fname, e))
try:
json_obj = json.loads(json_data)
dest.clear()
dest.update(json_obj)
self._mtimes[fname] = os.stat(fname).st_mtime
except Exception as e:
raise BackendIOException("Unable to parse JSON data from %s: %s" \
% (fname, e))
def _savejson(self, fname, obj):
"""Save obj in JSON format in a file in self._directory"""
fname = "%s/%s.json" % (self._directory, fname)
try:
with open("%s.tmp" % fname, 'w') as f:
json.dump(obj, f, cls=BytesEncoder)
f.flush()
shutil.move("%s.tmp" % fname, fname)
except Exception as e:
raise BackendIOException("Unable to save JSON file %s: %s" \
% (fname, e))
def save_users(self):
"""Save users in a JSON file"""
self._savejson(self._users_fname, self.users)
def save_roles(self):
"""Save roles in a JSON file"""
self._savejson(self._roles_fname, self.roles)
def save_pending_registrations(self):
"""Save pending registrations in a JSON file"""
self._savejson(self._pending_reg_fname, self.pending_registrations)