kuasha/cosmos

View on GitHub
cosmos/service/appservice.py

Summary

Maintainability
C
1 day
Test Coverage
"""
 Copyright (C) 2014 Maruf Maniruzzaman
 Website: http://cosmosframework.com
 Author: Maruf Maniruzzaman
 License :: OSI Approved :: MIT License
"""
import os
import json
import zipfile
import io
import datetime
from tornado import gen
import tornado.web
from cosmos.common.constants import *
from cosmos.service import requesthandler
from cosmos.service.utils import MongoObjectJSONEncoder

zip_file_root = COSMOS_SOURCE_FILE_ROOT_NAME


class AppInstallHandler(requesthandler.RequestHandler):

    @tornado.web.asynchronous
    @gen.coroutine
    def get(self):
        self.write('''
                <form enctype="multipart/form-data" method="POST">
                    Application package: <input name="application" type="file" /><br />
                    <input type="submit" value="Install" />
                </form>''')

        self.finish()

    @gen.coroutine
    def import_bootstrap_objects(self, object_service, object_name, object_data):
        user = self.current_user
        for data in object_data:
            object_service.save(user, object_name, data)

        if object_name == COSMOS_APPLICATION_OBJECT_NAME and len(object_data) == 1:
            first_app = object_data[0]
            self.setup_global_settings(first_app)

    @gen.coroutine
    def import_setting(self, object_service, app_zip_file, name, values):
        if name == "bootstrap_objects":
            for object_defn in values:
                object_name = object_defn["object"]
                object_data = object_defn["data"]

                self.import_bootstrap_objects(object_service, object_name, object_data)
        if name == "file_objects":
            self.import_files(object_service, values, app_zip_file)

        if name == "source_files":
            self.import_source_files(values, app_zip_file)

    @gen.coroutine
    def import_files(self, object_service, files_objects, app_zip_file):
        for file_object in files_objects[0]:
            collection_name = file_object["collection_name"]
            file_id = file_object["file_id"]
            filename = file_object["filename"]
            content_type= file_object["content_type"]
            file_path = collection_name+"/"+str(file_id)+"/"+filename
            object_data_file = app_zip_file.open(file_path)
            object_data = object_data_file.read()

            file_to_save = {"body": object_data, "content_type":content_type, "filename": filename}

            object_service.save_file(self.current_user, collection_name, file_to_save, file_id)

    @gen.coroutine
    def import_source_files(self, source_setttings, app_zip_file):
        if source_setttings:
            source_root = self.settings.get('source_root')
            for source_file_name in source_setttings:
                source_file_path = os.path.join(source_root, source_file_name)
                zip_file_path = os.path.join(zip_file_root, source_file_name)
                source_file_content_h = app_zip_file.open(zip_file_path)
                source_file_content = source_file_content_h.read()
                with open(source_file_path, 'w') as f:
                    f.write(source_file_content)

    @gen.coroutine
    def setup_global_settings(self, application_object):
        object_service = self.settings['object_service']
        app_id = application_object["id"]

        gs_to_save = {"defaultappid": app_id}

        gs_object_cursor = object_service.find(self.current_user, COSMOS_GLOBAL_SETTINGS_OBJECT_NAME, {}, ["defaultappid"])

        if gs_object_cursor and (yield gs_object_cursor.fetch_next):
            gs_object = gs_object_cursor.next_object()
            if not gs_object["defaultappid"]:
                gs_to_save['modifytime'] = str(datetime.datetime.now())
                yield object_service.update(self.current_user, COSMOS_GLOBAL_SETTINGS_OBJECT_NAME, str(gs_object["_id"]), gs_to_save)
        else:
            object_service.save(self.current_user, COSMOS_GLOBAL_SETTINGS_OBJECT_NAME, gs_to_save)


    @tornado.web.asynchronous
    @gen.coroutine
    def post(self):
        application_file = self.request.files["application"][0]['body']

        try:
            app_zip_file = zipfile.ZipFile(io.BytesIO(application_file))
        except zipfile.BadZipfile as bzf:
            raise tornado.web.HTTPError(400, bzf.message)


        object_data_file = app_zip_file.open(COSMOS_OBJECT_DATA_FILE_NAME)

        if not object_data_file:
            raise tornado.web.HTTPError(400, COSMOS_OBJECT_DATA_FILE_NAME + " not present in archive.")

        object_data = object_data_file.read()

        object_data_json = json.loads(object_data.decode("utf-8"))

        object_service = self.settings['object_service']

        for setting in object_data_json["settings"]:
            setting_name = setting["name"]
            value = setting["value"]
            self.import_setting(object_service, app_zip_file, setting_name, value)

        application_data_file = app_zip_file.open(COSMOS_APPLICATION_FILE_NAME)

        if not application_data_file:
            raise tornado.web.HTTPError(400, COSMOS_APPLICATION_FILE_NAME + " not present in archive.")

        application_data = application_data_file.read()
        application_data_json = json.loads(application_data.decode("utf-8"))

        self.import_bootstrap_objects(object_service, COSMOS_APPLICATION_OBJECT_NAME, [application_data_json])

        data = {"installed": True}

        self.write(data)
        self.finish()

#TODO: For performance reason this should be done as background process
#TODO: This is an extremely big function -split up
class AppPackageHandler(requesthandler.RequestHandler):
    @tornado.web.asynchronous
    @gen.coroutine
    def get(self, application_id):

        object_service = self.settings['object_service']

        query = {"id": application_id}

        app_cursor = object_service.find(self.current_user, COSMOS_APPLICATION_OBJECT_NAME, query, [])

        if (yield app_cursor.fetch_next):
            application = app_cursor.next_object()
        else:
            raise tornado.web.HTTPError(404, "Application not found")

        mf = io.BytesIO()

        with zipfile.ZipFile(mf, mode='w', compression=zipfile.ZIP_DEFLATED) as zf:

            zf.writestr(COSMOS_APPLICATION_FILE_NAME, MongoObjectJSONEncoder().encode(application))

            settings = application.get("settings")
            if settings:
                object_settings = settings.get("objects")
                object_map = settings.get("objectmap")
                object_settings = self.make_object_settings(object_map, object_settings)

                # Package object data
                if object_settings:
                    bootstrap_objects = yield self.load_bootstrap_objects(application_id, object_settings)
                else:
                    bootstrap_objects = []

                file_objects_data = []

                # Package grid files
                file_settings = settings.get("file_objects")
                if not file_settings:
                    file_settings = []

                source_modules = None
                for bootstrap_object in bootstrap_objects:
                    if bootstrap_object["object"] == COSMOS_SOURCE_MODULES_OBJECT_NAME:
                        source_modules = bootstrap_object["data"]

                if source_modules:
                    for source_module in source_modules:
                        if "gridfile" == source_module.get("type"):
                            file_id = source_module.get("file_id")
                            file_name = source_module.get("filename")
                            if file_id:
                                yield self.load_grid_file(zf, object_service, {"file_id":file_id, "filename": file_name, "collection_name" : COSMOS_SOURCE_MODULES_OBJECT_NAME})

                if file_settings and len(file_settings) > 0:
                    file_objects_data = yield self.load_grid_file_objects(zf, object_service, file_settings)

                # Package source files from disk
                source_setttings = settings.get("source_code")
                source_file_config = []

                source_root = self.settings.get('source_root')
                if source_setttings:
                    source_file_config = self.load_disk_source_files(zf, source_root, source_setttings)

                application_cab = MongoObjectJSONEncoder().encode({
                    "exporttime": str(datetime.datetime.utcnow()),
                    "archive_version": COSMOS_CURRENT_ARCHIVE_VERSION,
                    "settings": [
                        {"name": "bootstrap_objects", "value": bootstrap_objects},
                        {"name": "file_objects", "value": file_objects_data},
                        {"name": "source_files", "directory": zip_file_root, "value": source_file_config}
                    ]
                })

                zf.writestr(COSMOS_OBJECT_DATA_FILE_NAME, application_cab)

        self.set_header("Content-Type", "application/zip")
        self.set_header("content-disposition", "attachment; filename='" + application_id + ".xapp'")
        self.write(mf.getvalue())

    def make_object_settings(self, object_map, object_settings):
        if not object_settings:
            object_settings = []

        object_settings.append(COSMOS_WIDGETS_OBJECT_NAME)
        object_settings.append(COSMOS_APPLICATION_ENDPOINT_LIST_OBJECT_NAME)
        object_settings.append(COSMOS_INTERCEPTOR_OBJECT_NAME)
        object_settings.append(COSMOS_SOURCE_MODULES_OBJECT_NAME)

        if object_map:
            listconfigobject = object_map.get("listconfigobject")

            if listconfigobject:
                object_settings.append(listconfigobject)

            singleitemconfigobject = object_map.get("singleitemconfigobject")
            if singleitemconfigobject:
                object_settings.append(singleitemconfigobject)

            pageconfigobject = object_map.get("pageconfigobject")
            if pageconfigobject:
                object_settings.append(pageconfigobject)

            chartconfigobject = object_map.get("chartconfigobject")
            if chartconfigobject:
                object_settings.append(chartconfigobject)

            widgetobject = object_map.get("widgetobject")
            if widgetobject:
                object_settings.append(widgetobject)

            formconfigobject = object_map.get("formconfigobject")
            if formconfigobject:
                object_settings.append(formconfigobject)

            menuconfigobject = object_map.get("menuconfigobject")
            if menuconfigobject:
                object_settings.append(menuconfigobject)

        return object_settings

    def load_disk_source_files(self, archive_file, source_root, source_setttings):
        source_file_config = []

        for source_file_name in source_setttings:
            self.validate_source_file_name(source_file_name)

            source_file_path = os.path.join(source_root, source_file_name)
            zip_file_path = os.path.join(zip_file_root, source_file_name)
            source_file_config.append(source_file_name)
            with open(source_file_path, 'r') as content_file:
                content = content_file.read()
                archive_file.writestr(zip_file_path, content)

        return source_file_config

    @gen.coroutine
    def load_grid_file(self, archive_file, object_service, file_def):
        file_id = file_def["file_id"]
        collection_name = file_def.get("collection_name")
        filename = file_def["filename"]

        ignore_col_name = False
        if (not collection_name) or (collection_name == COSMOS_SOURCE_MODULES_OBJECT_NAME):
            ignore_col_name = True

        promise = object_service.load_file(self.current_user, collection_name, file_id, ignore_col_name)
        result = yield promise

        file_path = collection_name + "/" + str(file_id) + "/" + filename
        archive_file.writestr(file_path, result.get("body"))

    @gen.coroutine
    def load_grid_file_objects(self, archive_file, object_service, file_settings):
        file_objects_data = []
        for directory_object_name in file_settings:
            promise = object_service.list_file(self.current_user, directory_object_name)
            file_list = yield promise

            file_objects_data.append(file_list)

            for file_def in file_list:
                yield self.load_grid_file(self, archive_file, object_service, file_def)

        raise gen.Return(file_objects_data)

    @gen.coroutine
    def load_bootstrap_objects(self, application_id, object_settings):
        bootstrap_objects = []
        for object_name in object_settings:
            object_service = self.settings['object_service']
            query = self.get_query(object_name, application_id)
            object_data_cursor = object_service.find(self.current_user, object_name, query, [])

            result_list = []
            while (yield object_data_cursor.fetch_next):
                qry_result = object_data_cursor.next_object()
                qry_result["_id"] = str(qry_result["_id"])
                result_list.append(qry_result)

            dt = {"object": object_name, "data": result_list}
            bootstrap_objects.append(dt)

        raise gen.Return(bootstrap_objects)

    def validate_source_file_name(self, source_file_name):
        # TODO: Make sure path does not contain .. or similar things or start with root ( / or drive letter)
        pass

    def get_query(self, object_name, application_id):
        if object_name and object_name.startswith("cosmos."):
            return {"app_id": application_id}

        return {}