示例#1
0
class BoxItem():

    BOX_FOLDER = "folder"
    BOX_FILE = "file"
    BOX_ERR_NOT_FOUND = 404
    BOX_ERR_CONFLICT = 409
    BOX_ERR_RESERVED = 'name_temporarily_reserved'
    BOX_ERR_DUPLICATE = 'item_name_in_use'

    def __init__(self, cache_file_name, root, client):
        self.path = ''
        self.id = "0"
        self.type = self.BOX_FOLDER
        self.modified_at = None
        self.size = 0
        self.cache = CacheHandler(cache_file_name)
        self.root = root
        self.client = client

    def get_by_path(self,
                    path,
                    create_if_not_exist=False,
                    force_no_cache=False):
        rel_path = get_rel_path(path)
        if rel_path == '':
            self.set_root()
            return self

        item_id, item_type = self.cache.query(rel_path, force_no_cache)

        if item_id is not None:
            try:
                item = self.get_details(item_id, item_type)
                self.path = rel_path
                self.id = item_id
                self.type = item_type
                self.size = (item.size if self.is_file() else 0)
                return self
            except Exception as error:
                logger.info("Exception:{}".format(error))
                self.cache.reset()

        # Start iterating path from root id "0"
        item_id = '0'
        item_type = self.BOX_FOLDER

        elts = rel_path.split('/')

        current_path = ''
        for elt in elts:
            current_path = os.path.join(current_path, elt)
            items_iter = self.client.folder(folder_id=item_id).get_items(
                fields=['modified_at', 'name', 'type', 'size'])
            found = False
            for item in items_iter:
                if item.name == elt:
                    self.path = rel_path
                    self.id = item.id
                    item_id = item.id
                    self.type = item.type
                    self.modified_at = self.format_date(item.modified_at)
                    self.size = item.size
                    self.cache.add(current_path, item.id, item.type)
                    found = True
                    break

            if not found:
                if create_if_not_exist:
                    new_folder = self.create_subfolder(elt)
                    item_id = new_folder.id
                    self.cache.add(current_path, new_folder.id,
                                   self.BOX_FOLDER)
                else:
                    self.set_none()
        return self

    def get_details(self, id, type):
        if type == self.BOX_FOLDER:
            return self.client.folder(id).get(
                fields=['modified_at', 'name', 'type', 'size'])
        elif type == self.BOX_FILE:
            return self.client.file(id).get(
                fields=['modified_at', 'name', 'type', 'size'])

    def set_root(self):
        self.id = "0"
        self.type = self.BOX_FOLDER
        self.size = 0

    def set_none(self):
        self.id = None
        self.type = None
        self.modified_at = None
        self.size = None

    def create_subfolder(self, name):
        new_folder = {}
        new_id = None
        while new_id is None:
            try:
                new_folder = self.client.folder(self.id).create_subfolder(name)
                new_id = self.fix_any_duplicate(name, new_folder['id'])
            except BoxAPIException as err:
                if err.status == self.BOX_ERR_CONFLICT:
                    if err.code == self.BOX_ERR_RESERVED:
                        # Item name is reserved but there is no ID yet, so we have to loop until we get a BOX_ERR_DUPLICATE
                        time.sleep(1)
                        pass
                    elif err.code == self.BOX_ERR_DUPLICATE:
                        new_id = err.context_info['conflicts'][0]['id']
                    else:
                        raise Exception(
                            'Unimplemented Box.com conflict error while creating subfolder'
                        )
                else:
                    raise Exception(
                        'Unimplemented Box.com error while creating subfolder')
        self.id = new_id
        self.type = self.BOX_FOLDER
        self.size = 0
        return self

    def get_last_modified(self, item=None):
        if item is None:
            return self.modified_at
        elif "modified_at" in item:
            return self.format_date(item["modified_at"])
        else:
            return

    def format_date(self, date):
        if date is not None:
            utc_time = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S-%f:00")
            epoch_time = (utc_time - datetime(1970, 1, 1)).total_seconds()
            return int(epoch_time) * 1000
        else:
            return None

    def not_exists(self):
        return (self.id == None)

    def exists(self):
        return (self.id is not None)

    def is_folder(self):
        return self.type == self.BOX_FOLDER

    def is_file(self):
        return self.type == self.BOX_FILE

    def get_stat(self):
        ret = {
            'path': get_normalized_path(self.path),
            'size': self.size if self.is_file() else 0,
            'isDirectory': self.is_folder()
        }
        if self.modified_at is not None:
            ret["lastModified"] = self.modified_at
        return ret

    def get_children(self, internal_path):
        full_path = get_full_path(self.root, self.path)
        intra_path = self.path.replace('/' + self.root, '')
        children = []
        for sub in self.client.folder(self.id).get_items(
                fields=['modified_at', 'name', 'type', 'size']):
            sub_path = get_normalized_path(
                os.path.join(internal_path, sub.name))
            ret = {
                'fullPath': sub_path,
                'exists': True,
                'directory': sub.type == self.BOX_FOLDER,
                'size': sub.size,
                'lastModified': self.get_last_modified(sub)
            }
            children.append(ret)
            self.cache.add(get_rel_path(sub.name), sub.id, sub.type)
        return children

    def get_id(self):
        return self.id

    def get_as_browse(self):
        return {
            'fullPath': get_normalized_path(self.path),
            'exists': self.exists(),
            'directory': self.is_folder(),
            'size': self.size,
            'lastModified': self.get_last_modified()
        }

    def get_stream(self, byte_range=None):
        if byte_range:
            ws = self.client.file(self.id).content(byte_range=byte_range)
        else:
            ws = self.client.file(self.id).content()
        return BytesIO(ws)

    def write_stream(self, stream):
        file_name = self.path.split('/')[-1]
        sio = BytesIO()
        shutil.copyfileobj(stream, sio)
        sio.seek(0)
        ret = self.client.folder(self.id).upload_stream(sio,
                                                        file_name=file_name)
        self.id = ret.id
        self.cache.add(self.path, ret.id, ret.type)
        return self

    def create_path(self, path, force_no_cache=False):
        target_path = '/'.join(path.split('/')[:-1])
        ret = self.get_by_path(target_path,
                               create_if_not_exist=True,
                               force_no_cache=force_no_cache)
        ret.path = path
        return ret

    def delete(self):
        if self.is_file():
            try:
                self.client.file(self.id).delete()
            except BoxAPIException as err:
                if err.status == self.BOX_ERR_NOT_FOUND:
                    # Probably deleted by competing process
                    pass
                else:
                    raise Exception("Error while deleting box.com item")
            self.cache.remove(self.id)
            return 1
        if self.is_folder():
            return self.recursive_delete()

    def recursive_delete(self, id=None):
        counter = 0
        if id is None:
            id = self.id
        try:
            for child in self.client.folder(id).get_items():
                if child.type == self.BOX_FOLDER:
                    counter = counter + self.recursive_delete(id=child.id)
                    try:
                        self.cache.remove(child.id)
                        counter = counter + 1
                    except Exception as error:
                        logger.info("Exception:{}".format(error))
                elif child.type == self.BOX_FILE:
                    try:
                        self.client.file(child.id).delete()
                        self.cache.remove(child.id)
                        counter = counter + 1
                    except Exception as error:
                        # File already deleted
                        logger.info("Exception:{}".format(error))
        except Exception as error:
            logger.info("Folder already deleted")
        self.cache.remove(self.id)
        return counter

    def fix_any_duplicate(self, name, new_id):
        # Several plugin instances creating the same folder on box.com can lead to duplicate folder names
        if self.is_duplicated(name, new_id):
            self.cache.reset()
            time.sleep(1)  # waiting for dust to settle on box.com side
            id_default_folder = self.id_default_folder(name)
            if id_default_folder != new_id:
                try:
                    self.client.folder(new_id).delete()
                except Exception as error:
                    logger.info("Folder already deleted:{}".format(error))
                return id_default_folder
        return new_id

    def is_duplicated(self, name, new_id):
        instances = 0
        my_child = False
        try:
            for child in self.client.folder(self.id).get_items():
                if child.name == name:
                    instances = instances + 1
                    if child.id == new_id:
                        my_child = True
        except BoxAPIException as err:
            raise Exception(
                'Error while accessing box.com item:{0}'.format(err))
        return (instances > 1) and my_child

    def id_default_folder(self, name):
        try:
            probe_folder = self.client.folder(self.id).create_subfolder(name)
            return probe_folder.id
        except BoxAPIException as err:
            if err.status == self.BOX_ERR_CONFLICT:
                if err.code == self.BOX_ERR_DUPLICATE:
                    return err.context_info['conflicts'][0]['id']
                else:
                    raise Exception(
                        'Unimplemented Box.com conflict error while creating subfolder'
                    )
            else:
                raise Exception(
                    'Unimplemented Box.com error while creating subfolder: {0}'
                    .format(err))
        return None

    def check_path_format(self, path):
        special_names = [".", ".."]
        if not all(c in string.printable for c in path):
            raise Exception('The path contains non-printable char(s)')
        for element in path.split('/'):
            if len(element) > 255:
                raise Exception(
                    'An element of the path is longer than the allowed 255 characters'
                )
            if element in special_names:
                raise Exception(
                    'Special name "{0}" is not allowed in a box.com path'.
                    format(element))
            if element.endswith(' '):
                raise Exception(
                    'An element of the path contains a trailing space')

    def close(self):
        self.cache.write_onto_disk()