"""AssetUpload Model.""" from flask import g from sqlalchemy import and_ from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy import Enum from sqlalchemy import Integer from sqlalchemy import or_ from sqlalchemy import String from sqlalchemy.orm import relationship from sqlalchemy.sql import func from asset_transcoder.connectors import mysql from asset_transcoder.constants import asset_status as asset_status_consts from asset_transcoder.constants import asset_types as asset_types_consts from asset_transcoder.constants import asset_upload as asset_upload_consts from asset_transcoder.constants import error from asset_transcoder.models import asset_final from asset_transcoder.models import asset_status from asset_transcoder.utils import api_utils from asset_transcoder.utils.asset_type import map_asset_to_file_types from asset_transcoder.utils.exceptions import OwsError class AssetUpload(mysql.BaseModel): """Table definition for asset_upload table.""" __tablename__ = 'asset_upload' asset_upload_id = Column( 'id', Integer, primary_key=True, autoincrement=True) user_id = Column(String(200), nullable=False) asset_type = Column(String(16)) filename = Column(String(64), nullable=False) object_id = Column(String(255), nullable=True) object_type = Column(Enum(*asset_upload_consts.OBJECT_TYPES), nullable=True) original_filename = Column(String(255), nullable=True) deleted = Column(Boolean, default=False) created_date = Column(DateTime, default=func.now()) updated_date = Column( DateTime, default=func.now(), onupdate=func.now()) assets_final = relationship(asset_final.AssetFinal, lazy='joined') is_duplicated = Column(Boolean, default=False) def to_dict(self): """Return object as dict. Returns: dict: Dictionary representation of object """ return { 'id': self.asset_upload_id, 'user_id': self.user_id, 'asset_type': self.asset_type, 'filename': self.filename, 'object_id': self.object_id, 'object_type': self.object_type, 'created_date': self.created_date, 'updated_date': self.updated_date, 'original_filename': self.original_filename, 'media_type': self.media_type() } def media_type(self): """Get the overall type of the asset.""" if self.asset_type in asset_types_consts.IMAGE_FILE_TYPES: return 'image' if self.asset_type in asset_types_consts.AUDIO_FILE_TYPES: return 'audio' return 'unknown' def to_dict_with_finals(self): """Return asset upload with asset finals as dict. Returns: dict: Dictionary representation of object """ results = self.to_dict() results['asset_finals'] = [{ 'id': final.asset_final_id, 'asset_type': final.asset_type, 'asset_subtype': final.asset_subtype, 'filename': final.filename, 'url': api_utils.asset_url(final.filename, self.object_type), 'duration': final.duration } for final in self.assets_final] return results def create_asset_upload(payload, session=None): """Put new raw asset item into asset_upload table. Args: payload (dict): Asset fields values. session (sqlalchemy.Session): db connection. Returns: dict: created asset upload. """ asset_upload = AssetUpload(**payload) if session is None: with mysql.transcoder_db_session() as session: session.add(asset_upload) else: session.add(asset_upload) session.flush() return asset_upload.to_dict() def update_asset_upload(asset_upload_id, payload): """Update asset item with new values from payload. Args: asset_upload_id (ind): Unique identofier of asset_upload payload (dict): Asset fields values Returns: dict: containing a dict with the update asset_upload. """ with mysql.transcoder_db_session() as session: query = session.query( AssetUpload).filter(AssetUpload.asset_upload_id == asset_upload_id) query.update(payload) result = query.first() if result: return result.to_dict() raise OwsError.not_found(message=error.ERROR_ASSET_UPLOAD_NOT_FOUND) def delete_previous_assets(current_asset_upload_id, object_type, object_id, asset_type): """Mark all previous asset_uploads as deleted except latest one.""" with mysql.transcoder_db_session() as session: asset_type_range = map_asset_to_file_types(asset_type) filters = [ (AssetUpload.object_id == object_id), (AssetUpload.object_type == object_type), (AssetUpload.deleted.is_(False)), (AssetUpload.asset_upload_id != current_asset_upload_id), (AssetUpload.asset_type.in_(asset_type_range)) ] for existing_asset in session.query(AssetUpload).filter(*filters).all(): existing_asset.deleted = True g.log.info( f'Previous asset: {existing_asset.asset_upload_id} of asset_type: {existing_asset.asset_type} ' f'of {object_type}: {object_id} is replaced.') def delete_asset_upload(asset_types, object_id, object_type): """Delete the current asset_upload by params. Args: asset_types (list): list of asset_types to be matched for deletion object_id (str): Unique identifier of the object object_type (str): Type of the object """ with mysql.transcoder_db_session() as session: filters = [ (AssetUpload.asset_type.in_(asset_types)), (AssetUpload.object_id == object_id), (AssetUpload.object_type == object_type), (AssetUpload.deleted.is_(False)), ] result_items = session.query(AssetUpload).filter(*filters).all() for item in result_items: item.deleted = True g.log.info( f'Asset: {item.asset_upload_id} of asset_type: {item.asset_type} of {object_type}: {object_id} ' f'is deleted.') if not result_items: raise OwsError.not_found(error.ERROR_ASSET_UPLOAD_NOT_FOUND) items = [res.to_dict() for res in result_items] return {'items': items} def get_asset_upload(filename, user_id=None): """Return asset_upload item. Args: filename (str): unique filename user_id (str): user id Returns: dict: containing a dict with the retrieved asset_upload. """ with mysql.transcoder_db_session(read_only=True) as session: filters = [ (AssetUpload.filename == filename), (AssetUpload.deleted.is_(False)) ] if user_id: filters.extend([ (AssetUpload.user_id == user_id) ]) asset_upload = session.query(AssetUpload).filter(*filters).first() if asset_upload is None: raise OwsError.not_found(message=error.ERROR_ASSET_UPLOAD_NOT_FOUND) return asset_upload def get_object_assets(object_id, object_type): """Return assets for an episode that are already in assets_final. Args: object_id (int): The unique identifier of the object object_type (str): Type of the requested object Returns: dict: dict with a list of assets. """ result = get_objects_assets([object_id], [object_type]) if not len(result['items']): raise OwsError.not_found(error.ERROR_ASSET_FINAL_NOT_FOUND) return result def get_objects_assets(object_ids, object_types): """Return assets for an episode that are already in assets_final. Args: object_ids (list): The unique identifiers of the objects. object_types (list): Types of the requested objects. Returns: dict: dict with episode assets """ if not object_ids: return {'items': []} with mysql.transcoder_db_session(read_only=True) as session: object_matches = [] for object_id, object_type in zip(object_ids, object_types): object_matches.append( and_( (AssetUpload.object_id == object_id), (AssetUpload.object_type == object_type), ) ) filters = [ (AssetUpload.deleted.is_(False)), or_(*object_matches) ] results = session.query(AssetUpload).filter(*filters).all() return _create_response_with_status(results, session) def get_asset_by_id_and_type(object_id, object_type, asset_type): """Return asset upload dict whose encoding is completed by object_id, object_type and asset_type. Get asset upload by object_id, object_type, asset_type and not deleted. If not, raise 404. Check if the asset upload has the last status as endoding_completed in the asset_status table. If not, raise 404 with error message encoding not completed. Args: object_id (int): Unique identifier of the object in system. object_type (str): Type of object like episode or podcast. asset_type (list of str): file type like ['WAV'] for episode or ['JPG'] and ['TIF', 'JPG'] for podcast. Returns: dict: dict with asset upload record. """ with mysql.transcoder_db_session(read_only=True) as session: asset_upload = session.query(AssetUpload).filter( AssetUpload.object_id == object_id, AssetUpload.object_type == object_type, AssetUpload.asset_type.in_(asset_type), AssetUpload.deleted.is_(False) ).first() if asset_upload is None: raise OwsError.not_found(message=error.ERROR_ASSET_UPLOAD_NOT_FOUND) last_asset_status = asset_status.get_last_asset_status(asset_upload.asset_upload_id)['status'] if last_asset_status != asset_status_consts.STATUS_ENCODING_COMPLETED: raise OwsError.not_found(message=error.ERROR_ASSET_UPLOAD_NOT_ENCODED) return asset_upload.to_dict() def get_episodes_audio_assets(object_ids): """Return episodes audio assets with final assets data. Args: object_ids (list of int): List of unique identifier of the object in system. Returns: list of dict: list of dict with episode's wav asset upload and it's corresponding final_assets. """ with mysql.transcoder_db_session(read_only=True) as session: assets_upload = session.query(AssetUpload).filter( AssetUpload.object_id.in_(object_ids), AssetUpload.object_type == asset_upload_consts.OBJECT_TYPE_EPISODE, AssetUpload.asset_type == asset_types_consts.TYPE_FILE_WAV, AssetUpload.deleted.is_(False) ).all() return [asset_upload.to_dict_with_finals() for asset_upload in assets_upload] def _create_response_with_status(results, session): asset_upload_ids = [] result_response = {'items': []} upload_id_status = {} for asset in results: asset_data = asset.to_dict_with_finals() if not asset_data['asset_finals']: asset_upload_ids.append(asset_data['id']) asset_data['status'] = None else: asset_data['status'] = asset_status_consts.STATUS_ENCODING_COMPLETED result_response['items'].append(asset_data) if asset_upload_ids: statuses = asset_status.get_last_status_by_ids( asset_upload_ids, session)['items'] upload_id_status = {item['asset_upload_id']: item['status'] for item in statuses} for asset_data in result_response['items']: if asset_data['id'] in upload_id_status: asset_data['status'] = upload_id_status[asset_data['id']] return result_response