# Copyright 2012, <NAME>
# Code at http://inamidst.com/duxlot/
# Apache License 2.0

class Storage(object):
    __slots__ = ("__attributes",)

    def __init__(self, attributes=None):
        if isinstance(attributes, dict):
            attributes = attributes.copy()
        elif attributes is None:
            attributes = {}
        else:
            name = type(attributes).__name__
            raise TypeError("expected 'dict', got '%s'" % name)
        object.__setattr__(self, "_Storage__attributes", attributes)

    def __str__(self):
        # So how come .__class__ works here?
        return "%s(**%s)" % (self.__class__.__name__, self())

    def __getattr__(self, name):
        return object.__getattribute__(self, "_Storage__attributes")[name]

    def __setattr__(self, name, value):
        object.__getattribute__(self, "_Storage__attributes")[name] = value

    def __contains__(self, name):
        return name in object.__getattribute__(self, "_Storage__attributes")

    def __call__(self, name=None, default=None):
        attributes = object.__getattribute__(self, "_Storage__attributes")
        if name is None:
            return attributes.copy()
        if name in attributes:
            return attributes[name]
        return default

class FrozenStorage(Storage):
    def __setattr__(self, name, value):
        raise AttributeError("'FrozenStorage' attributes cannot be set")

def populate():
    global filesystem
    global output

    import contextlib
    import multiprocessing

    filesystem = Storage()

    filesystem.lock = multiprocessing.RLock()

    @contextlib.contextmanager
    def filesystem_open(*args, **kargs):
        with filesystem.lock:
            yield open(*args, **kargs)
    filesystem.open = filesystem_open

    output = Storage()

    output.lock = multiprocessing.RLock()

    def output_write(*args, **kargs):
        with output.lock:
            print(*args, **kargs)
    output.write = output_write

populate()

del populate

def database(base, cache=None):
    import contextlib
    import json
    import multiprocessing
    import os.path
    import pickle

    if cache is None:
        cache = Storage()

    base = os.path.expanduser(base)
    dotdb = base + ".%s.db"
    dotjson = base + ".%s.json"

    def check(name):
        if not name.isalpha():
            raise ValueError(name)

    def do_load(name):
        filename = dotdb % name
        if os.path.isfile(filename):
            with filesystem.open(filename, "rb") as f:
                return pickle.load(f)

    def do_dump(name, data):
        with filesystem.open(dotdb % name, "wb") as f:
            pickle.dump(data, f)

    # @@ init, copies to cache returns a fallback?
    # e.g. irc.safe.database.init("name", [])

    def init(name, default=None):
        data = do_load(name) or default
        setattr(cache, name, data)
        if data is default:
            do_dump(name, data)

    def load(name):
        check(name)
        with filesystem.lock:
            return do_load(name)

    def dump(name, data):
        check(name)
        with filesystem.lock:
            do_dump(name, data)

    @contextlib.contextmanager
    def context(name):
        check(name)
        with filesystem.lock:
            data = getattr(cache, name)
            yield data
            setattr(cache, name, data)
            do_dump(name, data)

    def export(name): # remove this? export base instead?
        check(name)
        with filesystem.lock:
            data = do_load(name)
            filename = dotjson % name
            with filesystem.open(filename, "w", encoding="utf-8") as f:
                json.dump(data, f)
            return filename

    return FrozenStorage({
        "init": init,
        "load": load,
        "dump": dump,
        "context": context,
        "export": export,
        "cache": cache
    })

# Copyright 2012, <NAME>
# Code at http://inamidst.com/duxlot/
# Apache License 2.0

class Storage(object):
    __slots__ = ("__attributes",)

    def __init__(self, attributes=None):
        if isinstance(attributes, dict):
            attributes = attributes.copy()
        elif attributes is None:
            attributes = {}
        else:
            name = type(attributes).__name__
            raise TypeError("expected 'dict', got '%s'" % name)
        object.__setattr__(self, "_Storage__attributes", attributes)

    def __str__(self):
        # So how come .__class__ works here?
        return "%s(**%s)" % (self.__class__.__name__, self())

    def __getattr__(self, name):
        return object.__getattribute__(self, "_Storage__attributes")[name]

    def __setattr__(self, name, value):
        object.__getattribute__(self, "_Storage__attributes")[name] = value

    def __contains__(self, name):
        return name in object.__getattribute__(self, "_Storage__attributes")

    def __call__(self, name=None, default=None):
        attributes = object.__getattribute__(self, "_Storage__attributes")
        if name is None:
            return attributes.copy()
        if name in attributes:
            return attributes[name]
        return default

class FrozenStorage(Storage):
    def __setattr__(self, name, value):
        raise AttributeError("'FrozenStorage' attributes cannot be set")

def populate():
    global filesystem
    global output

    import contextlib
    import multiprocessing

    filesystem = Storage()

    filesystem.lock = multiprocessing.RLock()

    @contextlib.contextmanager
    def filesystem_open(*args, **kargs):
        with filesystem.lock:
            yield open(*args, **kargs)
    filesystem.open = filesystem_open

    output = Storage()

    output.lock = multiprocessing.RLock()

    def output_write(*args, **kargs):
        with output.lock:
            print(*args, **kargs)
    output.write = output_write

populate()

del populate

def database(base, cache=None):
    import contextlib
    import json
    import multiprocessing
    import os.path
    import pickle

    if cache is None:
        cache = Storage()

    base = os.path.expanduser(base)
    dotdb = base + ".%s.db"
    dotjson = base + ".%s.json"

    def check(name):
        if not name.isalpha():
            raise ValueError(name)

    def do_load(name):
        filename = dotdb % name
        if os.path.isfile(filename):
            with filesystem.open(filename, "rb") as f:
                return pickle.load(f)

    def do_dump(name, data):
        with filesystem.open(dotdb % name, "wb") as f:
            pickle.dump(data, f)

    # @@ init, copies to cache returns a fallback?
    # e.g. irc.safe.database.init("name", [])

    def init(name, default=None):
        data = do_load(name) or default
        setattr(cache, name, data)
        if data is default:
            do_dump(name, data)

    def load(name):
        check(name)
        with filesystem.lock:
            return do_load(name)

    def dump(name, data):
        check(name)
        with filesystem.lock:
            do_dump(name, data)

    @contextlib.contextmanager
    def context(name):
        check(name)
        with filesystem.lock:
            data = getattr(cache, name)
            yield data
            setattr(cache, name, data)
            do_dump(name, data)

    def export(name): # remove this? export base instead?
        check(name)
        with filesystem.lock:
            data = do_load(name)
            filename = dotjson % name
            with filesystem.open(filename, "w", encoding="utf-8") as f:
                json.dump(data, f)
            return filename

    return FrozenStorage({
        "init": init,
        "load": load,
        "dump": dump,
        "context": context,
        "export": export,
        "cache": cache
    })

# Copyright 2012, <NAME> # Code at http://inamidst.com/duxlot/ # Apache License 2.0 # So how come .__class__ works here? # @@ init, copies to cache returns a fallback? # e.g. irc.safe.database.init("name", []) # remove this? export base instead?