# Copyright 2005-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# Author(s): Brian Harring (ferringb@gentoo.org)

import dbm

try:
    import dbm.gnu as gdbm
except ImportError:
    gdbm = None

import os
import pickle

from portage.cache import cache_errors, fs_template


class database(fs_template.FsBased):
    validation_chf = "md5"
    chf_types = ("md5", "mtime")

    autocommits = True
    cleanse_keys = True
    serialize_eclasses = False

    def __init__(self, *args, **config):
        super().__init__(*args, **config)

        default_db = config.get("dbtype", "anydbm")
        if not default_db.startswith("."):
            default_db = "." + default_db

        self._db_path = os.path.join(
            self.location, fs_template.gen_label(self.location, self.label) + default_db
        )
        self.__db = None
        mode = "w"
        if dbm.whichdb(self._db_path) in ("dbm.gnu", "gdbm"):
            # Allow multiple concurrent writers (see bug #53607).
            mode += "u"
        try:
            # dbm.open() will not work with bytes in python-3.1:
            #   TypeError: can't concat bytes to str
            self.__db = dbm.open(self._db_path, mode, self._perms)
        except dbm.error:
            # XXX handle this at some point
            try:
                self._ensure_dirs()
                self._ensure_dirs(self._db_path)
            except OSError as e:
                raise cache_errors.InitializationError(self.__class__, e)

            # try again if failed
            try:
                if self.__db is None:
                    # dbm.open() will not work with bytes in python-3.1:
                    #   TypeError: can't concat bytes to str
                    if gdbm is None:
                        self.__db = dbm.open(self._db_path, "c", self._perms)
                    else:
                        # Prefer gdbm type if available, since it allows
                        # multiple concurrent writers (see bug #53607).
                        self.__db = gdbm.open(self._db_path, "cu", self._perms)
            except dbm.error as e:
                raise cache_errors.InitializationError(self.__class__, e)
        self._ensure_access(self._db_path)

    def __getstate__(self):
        state = self.__dict__.copy()
        # These attributes are not picklable, so they are automatically
        # regenerated after unpickling.
        state["_database__db"] = None
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        mode = "w"
        if dbm.whichdb(self._db_path) in ("dbm.gnu", "gdbm"):
            # Allow multiple concurrent writers (see bug #53607).
            mode += "u"
        self.__db = dbm.open(self._db_path, mode, self._perms)

    def iteritems(self):
        # dbm doesn't implement items()
        for k in self.__db.keys():
            yield (k, self[k])

    def _getitem(self, cpv):
        # we override getitem because it's just a cpickling of the data handed in.
        return pickle.loads(self.__db[cpv.encode("utf-8", "backslashreplace")])

    def _setitem(self, cpv, values):
        self.__db[cpv.encode("utf-8", "backslashreplace")] = pickle.dumps(
            values, pickle.HIGHEST_PROTOCOL
        )

    def _delitem(self, cpv):
        del self.__db[cpv]

    def __iter__(self):
        return iter(list(self.__db.keys()))

    def __contains__(self, cpv):
        return cpv in self.__db

    def __del__(self):
        if "__db" in self.__dict__ and self.__db is not None:
            self.__db.sync()
            self.__db.close()

    # TODO: do we need iteritems()?
    items = iteritems
