Improved support for files

We updated the way files are stored in the files collection. Any
derived variation of a file (different encoding or size) is stored as
new record, referencing the original as a parent.
We also added a generate_link method, which is in charge of providing
the client API with the actual link to the backend specified by the
file.
This commit is contained in:
Francesco Siddi 2015-09-08 15:06:45 +02:00
parent 421a9938ab
commit 7cecfbe4e0
3 changed files with 153 additions and 54 deletions

View File

@ -14,6 +14,7 @@ from bson import ObjectId
from flask import g from flask import g
from flask import request from flask import request
from flask import url_for
from pre_hooks import pre_GET from pre_hooks import pre_GET
from pre_hooks import pre_PUT from pre_hooks import pre_PUT
@ -289,6 +290,27 @@ def post_GET_user(request, payload):
app.on_post_GET_users += post_GET_user app.on_post_GET_users += post_GET_user
# Hook to check the backend of a file resource, to build an appropriate link
# that can be used by the client to retrieve the actual file.
def generate_link(backend, path):
if backend == 'pillar':
link = url_for('file_server.index', file_name=path, _external=True)
elif backend == 'cdnsun':
pass
else:
link = None
return link
def before_returning_file(response):
response['link'] = generate_link(response['backend'], response['path'])
def before_returning_files(response):
for item in response['_items']:
item['link'] = generate_link(item['backend'], item['path'])
app.on_fetched_item_files += before_returning_file
app.on_fetched_resource_files += before_returning_files
# The file_server module needs app to be defined # The file_server module needs app to be defined
from file_server import file_server from file_server import file_server

View File

@ -5,6 +5,8 @@ from flask.ext.script import Manager
manager = Manager(app) manager = Manager(app)
MONGO_HOST = os.environ.get('MONGO_HOST', 'localhost')
@manager.command @manager.command
def runserver(): def runserver():
try: try:
@ -37,7 +39,7 @@ def clear_db():
""" """
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
db.drop_collection('nodes') db.drop_collection('nodes')
db.drop_collection('node_types') db.drop_collection('node_types')
@ -50,7 +52,7 @@ def remove_properties_order():
"""Removes properties.order """Removes properties.order
""" """
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
nodes = db.nodes.find() nodes = db.nodes.find()
for node in nodes: for node in nodes:
@ -71,7 +73,7 @@ def upgrade_node_types():
""" """
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
node_types = db.node_types.find({}) node_types = db.node_types.find({})
old_ids = {} old_ids = {}
@ -84,7 +86,7 @@ def get_id(collection, name):
"""Returns the _id of the given collection """Returns the _id of the given collection
and name.""" and name."""
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
node = db[collection].find({'name': name}) node = db[collection].find({'name': name})
print (node[0]['_id']) print (node[0]['_id'])
@ -97,7 +99,7 @@ def manage_groups():
and add or remove the user from that group. and add or remove the user from that group.
""" """
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
print ("") print ("")
@ -562,6 +564,7 @@ def populate_node_types(old_ids={}):
group_node_type = { group_node_type = {
'name': 'group', 'name': 'group',
'description': 'Generic group node type', 'description': 'Generic group node type',
'parent': {},
'dyn_schema': { 'dyn_schema': {
'url': { 'url': {
'type': 'string', 'type': 'string',
@ -577,7 +580,6 @@ def populate_node_types(old_ids={}):
'type': 'string', 'type': 'string',
'maxlength': 256, 'maxlength': 256,
}, },
'parent': {}
}, },
'form_schema': { 'form_schema': {
'url': {}, 'url': {},
@ -586,9 +588,47 @@ def populate_node_types(old_ids={}):
}, },
} }
asset_node_type = {
'name': 'asset',
'description': 'Assets for Elephants Dream',
# This data type does not have parent limitations (can be child
# of any node). An empty parent declaration is required.
'parent': {},
'dyn_schema': {
'status': {
'type': 'string',
'allowed': [
'published',
'pending',
'processing'
],
},
# We expose the type of asset we point to. Usually image, video,
# zipfile, ect.
'contentType':{
'type': 'string'
},
# We point to the original file (and use it to extract any relevant
# variation useful for our scope).
'file': {
'type': 'objectid',
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
}
},
'form_schema': {
'status': {},
'contentType': {},
'file': {},
}
}
from pymongo import MongoClient from pymongo import MongoClient
client = MongoClient() client = MongoClient(MONGO_HOST, 27017)
db = client.eve db = client.eve
def mix_node_type(old_id, node_type_dict): def mix_node_type(old_id, node_type_dict):
@ -619,43 +659,60 @@ def populate_node_types(old_ids={}):
# upgrade(scene_node_type, old_ids) # upgrade(scene_node_type, old_ids)
# upgrade(act_node_type, old_ids) # upgrade(act_node_type, old_ids)
# upgrade(comment_node_type, old_ids) # upgrade(comment_node_type, old_ids)
upgrade(project_node_type, old_ids) # upgrade(project_node_type, old_ids)
upgrade(asset_node_type, old_ids)
@manager.command
def add_group():
owners_group = {
'name': 'owners',
'permissions': [
{'node_type': get_id('node_types', 'shot'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'task'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'scene'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'act'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'comment'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
]
}
post_item('groups', world_group)
@manager.command @manager.command
def migrate_custom(): def add_file():
from pymongo import MongoClient from datetime import datetime
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
client = MongoClient() video = {
db = client.eve 'name': 'Video test',
'description': 'Video test description',
group_node_type = { # 'parent': 'objectid',
'name': 'group', 'contentType': 'video/mp4',
'description': 'Generic group node type', # Duration in seconds, only if it's a video
'dyn_schema': { 'duration': 50,
'url': { 'size': '720p',
'type': 'string', 'format': 'mp4',
}, 'width': 1280,
'status': { 'height': 720,
'type': 'string', 'user': '552b066b41acdf5dec4436f2',
'allowed': [ 'length': 15000,
'published', 'uploadDate': datetime.strftime(
'pending' datetime.now(), RFC1123_DATE_FORMAT),
], 'md5': 'md5',
}, 'filename': 'The file name',
'notes': { 'backend': 'pillar',
'type': 'string', 'path': '0000.mp4',
'maxlength': 256,
},
'parent': {}
},
'form_schema': {
'url': {},
'status': {},
'notes': {},
},
} }
db.node_types.insert(group_node_type) r = post_item('files', video)
print r
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -170,6 +170,9 @@ nodes_schema = {
'type': 'integer', 'type': 'integer',
'minlength': 0, 'minlength': 0,
}, },
'revision': {
'type': 'integer',
},
'parent': { 'parent': {
'type': 'objectid', 'type': 'objectid',
'data_relation': { 'data_relation': {
@ -252,32 +255,45 @@ files_schema = {
'type': 'string', 'type': 'string',
'required': True, 'required': True,
}, },
# Preview parameters: # If the object has a parent, it is a variation of its parent. When querying
'is_preview': { # for a file we are going to check if the object does NOT have a parent. In
'type': 'boolean' # this case we will query for all files with the ObjectID as parent and we
# will aggregate them according of the type (if it's an image we will use
# some prefix, if it's a video we will combine the contentType and a custom
# prefix, such as 720p)
'parent': {
'type': 'objectid',
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
}, },
'size': { 'contentType': { # MIME type image/png video/mp4
'type': 'string',
'required': True,
},
# Duration in seconds, only if it's a video
'duration': {
'type': 'integer',
},
'size': { # xs, s, b, 720p, 2K
'type': 'string' 'type': 'string'
}, },
'format': { 'format': { # human readable format, like mp4, HLS, webm, mov
'type': 'string' 'type': 'string'
}, },
'width': { 'width': { # valid for images and video contentType
'type': 'integer' 'type': 'integer'
}, },
'height': { 'height': {
'type': 'integer' 'type': 'integer'
}, },
#
'user': { 'user': {
'type': 'objectid', 'type': 'objectid',
'required': True, 'required': True,
}, },
'contentType': { 'length': { # Size in bytes
'type': 'string',
'required': True,
},
'length': {
'type': 'integer', 'type': 'integer',
'required': True, 'required': True,
}, },
@ -296,14 +312,14 @@ files_schema = {
'backend': { 'backend': {
'type': 'string', 'type': 'string',
'required': True, 'required': True,
'allowed': ["attract-web", "attract"] 'allowed': ["attract-web", "pillar"]
}, },
'path': { 'path': {
'type': 'string', 'type': 'string',
'required': True, 'required': True,
'unique': True, 'unique': True,
}, },
'previews': { 'previews': { # Deprecated (see comments above)
'type': 'list', 'type': 'list',
'schema': { 'schema': {
'type': 'objectid', 'type': 'objectid',
@ -313,6 +329,10 @@ files_schema = {
'embeddable': True 'embeddable': True
} }
} }
},
# Preview parameters:
'is_preview': { # Deprecated
'type': 'boolean'
} }
} }