bbc_storage — BBc1 0.7 documentation

# Source code for bbc_storage

    # -*- coding: utf-8 -*-
    """
    Copyright (c) 2017 beyond-blockchain.org.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    """
    import binascii
    import os

    import sys
    sys.path.extend(["../../"])
    from bbc1.common import logger
    from bbc1.common.bbclib import StorageType
    from bbc1.common import bbclib

    class BBcStorage:
        """
        Storage manager
        """
        def __init__(self, config, core=None, loglevel="all", logname=None):
            self.logger = logger.get_logger(key="bbc_storage", level=loglevel, logname=logname)
            self.config = config
            self.core = core
            conf = self.config.get_config()
            if 'storage' in conf and 'root' in conf['storage']:
                if conf['storage']['root'].startswith("/"):
                    self.storage_root = conf['storage']['root'] + "/"
                else:
                    self.storage_root = conf['workingdir'] + "/"
            else:
                self.storage_root = conf['workingdir'] + "/"
            os.makedirs(self.storage_root, exist_ok=True)
            self.storage_type = dict()
            self.storage_path = dict()

        def set_storage_path(self, domain_id, asset_group_id, from_config=False,
                             storage_type=StorageType.FILESYSTEM, storage_path=None):
            """
            Set storage path for the domain

            :param domain_id:
            :param asset_group_id
            :param from_config:  If True, read setting from config and ignore storage_type and storage_path param
            :param storage_type: filesystem/HTTP-PUT/HTTP-POST/NONE
            :param storage_path:    directory path or URL
            :return: True if make dir is successful
            """
            if from_config:
                conf = self.config.get_asset_group_config(domain_id, asset_group_id)
                storage_type = conf['storage_type']
                storage_path = conf['storage_path']
            if storage_type == StorageType.NONE:
                self.storage_type.pop(domain_id, None)
                self.storage_path[domain_id] = None
                return True
            domain_id_str = binascii.b2a_hex(domain_id).decode('utf-8')
            asset_group_id_str = binascii.b2a_hex(asset_group_id).decode('utf-8')
            if storage_path is None:
                storage_path = self.storage_root + domain_id_str + "/" + asset_group_id_str + "/storage/"
            else:
                if not os.path.exists(storage_path):
                    os.mkdir(storage_path, 0o777)

            self.storage_type.setdefault(domain_id, dict())
            self.storage_type[domain_id][asset_group_id] = storage_type

            self.storage_path.setdefault(domain_id, dict())
            if storage_type == StorageType.FILESYSTEM:
                self.storage_path[domain_id][asset_group_id] = storage_path
                return os.makedirs(self.storage_path[domain_id][asset_group_id], exist_ok=True)
            # TODO: need to implement other types

        def get_storage_type(self, domain_id, asset_group_id):
            """
            Get storage type for the domain

            :param domain_id:
            :param asset_group_id:
            :return:
            """
            if domain_id in self.storage_type:
                return self.storage_type[domain_id].get(asset_group_id, None)
            return None

        def store_locally(self, domain_id, asset_group_id, asid, content):
            """
            Store data in local storage

            :param domain_id: domain to put in
            :param asset_group_id
            :param asid:
            :param content:
            :return:
            """
            if domain_id not in self.storage_type or asset_group_id not in self.storage_type[domain_id]:
                return True
            elif self.storage_type[domain_id][asset_group_id] == StorageType.FILESYSTEM:
                return self.store_in_filesystem(domain_id, asset_group_id, asid, content)

            self.logger.info("Not supported yet.")
            return False

        def store_in_filesystem(self, domain_id, asset_group_id, asid, content):
            """
            Store data in a file system

            :param domain_id: domain to put in
            :param asset_group_id
            :param asid:
            :param content:
            :return:
            """
            path = self.storage_path[domain_id][asset_group_id]+"/"+binascii.b2a_hex(asid).decode('utf-8')
            with open(path, 'wb') as f:
                try:
                    f.write(content)
                except:
                    return False
            return os.path.exists(path)

        def get_locally(self, domain_id, asset_group_id, asid):
            """
            Get the file with the asset_id from local storage

            :param domain_id: domain to search in
            :param asset_group_id
            :param asid:   file name
            :return:       the file content (None if not found)
            """
            if domain_id not in self.storage_type or asset_group_id not in self.storage_type[domain_id]:
                return None
            elif self.storage_type[domain_id][asset_group_id] == StorageType.FILESYSTEM:
                return self.get_in_filesystem(domain_id, asset_group_id, asid)

            self.logger.info("Not supported yet.")
            return False

        def get_in_filesystem(self, domain_id, asset_group_id, asid):
            """
            Get the file with the asset_id in a file system

            :param domain_id: domain to search in
            :param asset_group_id
            :param asid:   file name
            :return:       the file content (None if not found)
            """
            path = self.storage_path[domain_id][asset_group_id]+"/"+binascii.b2a_hex(asid).decode('utf-8')
            if os.path.exists(path):
                try:
                    with open(path, 'rb') as f:
                        content = f.read()
                    return content
                except:
                    pass
            return None

        def remove(self, domain_id, asset_group_id, asid):
            """
            Remove the file with the asset_id

            :param domain_id:
            :param asset_group_id
            :param asid:   file name
            :return:       True if succeeded
            """
            if domain_id not in self.storage_type or asset_group_id not in self.storage_type[domain_id]:
                return None
            # TODO: do we need this method? If so, removing remote resources is also needed
            path = self.storage_path[domain_id][asset_group_id]+"/"+binascii.b2a_hex(asid).decode('utf-8')
            if os.path.exists(path):
                os.remove(path)
            else:
                return False
            return not os.path.exists(path)