|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +# freeseer - vga/presentation capture software |
| 5 | +# |
| 6 | +# Copyright (C) 2014 Free and Open Source Software Learning Centre |
| 7 | +# http://fosslc.org |
| 8 | +# |
| 9 | +# This program is free software: you can redistribute it and/or modify |
| 10 | +# it under the terms of the GNU General Public License as published by |
| 11 | +# the Free Software Foundation, either version 3 of the License, or |
| 12 | +# (at your option) any later version. |
| 13 | +# |
| 14 | +# This program is distributed in the hope that it will be useful, |
| 15 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | +# GNU General Public License for more details. |
| 18 | +# |
| 19 | +# You should have received a copy of the GNU General Public License |
| 20 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 21 | + |
| 22 | +# For support, questions, suggestions or any other inquiries, visit: |
| 23 | +# http://wiki.github.com/Freeseer/freeseer/ |
| 24 | + |
| 25 | +import functools |
| 26 | +import json |
| 27 | +import os |
| 28 | +import signal |
| 29 | +import sys |
| 30 | + |
| 31 | +from flask import current_app |
| 32 | +from flask import Flask |
| 33 | +from flask import jsonify |
| 34 | +from flask import request |
| 35 | + |
| 36 | +from freeseer import settings |
| 37 | +from freeseer.framework.multimedia import Multimedia |
| 38 | +from freeseer.framework.plugin import PluginManager |
| 39 | +from freeseer.frontend.controller import validate |
| 40 | + |
| 41 | +app = Flask(__name__) |
| 42 | + |
| 43 | + |
| 44 | +class HTTPError(Exception): |
| 45 | + |
| 46 | + def __init__(self, message, status_code): |
| 47 | + super(HTTPError, self).__init__(message) |
| 48 | + self.message = message |
| 49 | + self.status_code = status_code |
| 50 | + |
| 51 | + |
| 52 | +class ServerError(Exception): |
| 53 | + |
| 54 | + def __init__(self, message): |
| 55 | + super(ServerError, self).__init__(message) |
| 56 | + |
| 57 | + |
| 58 | +def catch_exceptions(func): |
| 59 | + @functools.wraps(func) |
| 60 | + def wrapper(*args, **kwargs): |
| 61 | + try: |
| 62 | + return func(*args, **kwargs) |
| 63 | + except HTTPError as e: |
| 64 | + return e.message, e.status_code |
| 65 | + return wrapper |
| 66 | + |
| 67 | + |
| 68 | +@app.route('/recordings', methods=['GET']) |
| 69 | +def get_all_recordings(): |
| 70 | + response = jsonify({'recordings': current_app.media_dict.keys()}) |
| 71 | + response.status_code = 200 |
| 72 | + return response |
| 73 | + |
| 74 | + |
| 75 | +@app.route('/recordings/<int:recording_id>', methods=['GET']) |
| 76 | +@catch_exceptions |
| 77 | +def get_specific_recording(recording_id): |
| 78 | + response = '' |
| 79 | + |
| 80 | + recording_id = int(recording_id) |
| 81 | + if recording_id in current_app.media_dict: |
| 82 | + retrieved_media_entry = current_app.media_dict[recording_id] |
| 83 | + retrieved_media = retrieved_media_entry['media'] |
| 84 | + retrieved_filename = retrieved_media_entry['filename'] |
| 85 | + |
| 86 | + state_indicator = retrieved_media.current_state |
| 87 | + if state_indicator == Multimedia.NULL: |
| 88 | + state = 'NULL' |
| 89 | + elif state_indicator == Multimedia.RECORD: |
| 90 | + state = 'RECORD' |
| 91 | + elif state_indicator == Multimedia.PAUSE: |
| 92 | + state = 'PAUSE' |
| 93 | + elif state_indicator == Multimedia.STOP: |
| 94 | + state = 'STOP' |
| 95 | + else: |
| 96 | + raise HTTPError('recording state could not be determined', 500) |
| 97 | + |
| 98 | + if os.path.isfile(retrieved_media_entry['filepath']): |
| 99 | + filesize = os.path.getsize(retrieved_media_entry['filepath']) |
| 100 | + else: |
| 101 | + filesize = 'NA' |
| 102 | + |
| 103 | + response = jsonify({ |
| 104 | + 'id': recording_id, |
| 105 | + 'filename': retrieved_filename, |
| 106 | + 'filesize': filesize, |
| 107 | + 'status': state |
| 108 | + }) |
| 109 | + response.status_code = 200 |
| 110 | + |
| 111 | + else: |
| 112 | + raise HTTPError('recording id could not be found', 404) |
| 113 | + |
| 114 | + return response |
| 115 | + |
| 116 | + |
| 117 | +@app.route('/recordings/<int:recording_id>', methods=['PATCH']) |
| 118 | +@catch_exceptions |
| 119 | +def control_recording(recording_id): |
| 120 | + response = '' |
| 121 | + |
| 122 | + recording_id = int(recording_id) |
| 123 | + if recording_id in current_app.media_dict: |
| 124 | + retrieved_media_entry = current_app.media_dict[recording_id] |
| 125 | + retrieved_media = retrieved_media_entry['media'] |
| 126 | + if validate.validate_control_recording_request_form(request.form): |
| 127 | + command = request.form['command'] |
| 128 | + media_state = retrieved_media.current_state |
| 129 | + |
| 130 | + if command == 'start' and media_state in [Multimedia.NULL, Multimedia.PAUSE]: |
| 131 | + retrieved_media.record() |
| 132 | + response = '', 200 |
| 133 | + elif command == 'pause' and media_state == Multimedia.RECORD: |
| 134 | + retrieved_media.pause() |
| 135 | + response = '', 200 |
| 136 | + elif command == 'stop' and media_state in [Multimedia.RECORD, Multimedia.PAUSE]: |
| 137 | + retrieved_media.stop() |
| 138 | + response = '', 200 |
| 139 | + else: |
| 140 | + raise HTTPError('command could not be performed', 400) |
| 141 | + else: |
| 142 | + raise HTTPError('Form data was invalid', 400) |
| 143 | + |
| 144 | + else: |
| 145 | + raise HTTPError('recording id could not be found', 404) |
| 146 | + |
| 147 | + return response |
| 148 | + |
| 149 | + |
| 150 | +@app.route('/recordings', methods=['POST']) |
| 151 | +@catch_exceptions |
| 152 | +def create_recording(): |
| 153 | + response = '' |
| 154 | + |
| 155 | + if validate.validate_create_recording_request_form(request.form): |
| 156 | + new_filename = request.form['filename'] |
| 157 | + new_media = Multimedia(current_app.record_config, current_app.record_plugin_manager) |
| 158 | + success, filename = new_media.load_backend(None, new_filename) |
| 159 | + |
| 160 | + if success: |
| 161 | + filepath = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location |
| 162 | + |
| 163 | + new_recording_id = current_app.next_id |
| 164 | + current_app.next_id = current_app.next_id + 1 |
| 165 | + |
| 166 | + if new_recording_id not in current_app.media_dict: |
| 167 | + current_app.media_dict[new_recording_id] = { |
| 168 | + 'media': new_media, |
| 169 | + 'filename': filename, |
| 170 | + 'filepath': filepath |
| 171 | + } |
| 172 | + |
| 173 | + response = jsonify({'id': new_recording_id}) |
| 174 | + response.status_code = 201 |
| 175 | + else: |
| 176 | + raise HTTPError('Provided id already in use', 500) |
| 177 | + else: |
| 178 | + raise HTTPError('Could not load multimedia backend', 500) |
| 179 | + else: |
| 180 | + raise HTTPError('Form data was invalid', 400) |
| 181 | + |
| 182 | + return response |
| 183 | + |
| 184 | + |
| 185 | +@app.route('/recordings/<int:recording_id>', methods=['DELETE']) |
| 186 | +@catch_exceptions |
| 187 | +def delete_recording(recording_id): |
| 188 | + recording_id = int(recording_id) |
| 189 | + if recording_id in current_app.media_dict: |
| 190 | + retrieved_media_entry = current_app.media_dict[recording_id] |
| 191 | + retrieved_media = retrieved_media_entry['media'] |
| 192 | + |
| 193 | + if retrieved_media.current_state == Multimedia.RECORD or retrieved_media.current_state == Multimedia.PAUSE: |
| 194 | + retrieved_media.stop() |
| 195 | + |
| 196 | + # Delete the file if it exists |
| 197 | + if os.path.isfile(retrieved_media_entry['filepath']): |
| 198 | + os.remove(retrieved_media_entry['filepath']) |
| 199 | + |
| 200 | + del current_app.media_dict[recording_id] |
| 201 | + response = '', 200 |
| 202 | + else: |
| 203 | + raise HTTPError('recording id could not be found', 404) |
| 204 | + |
| 205 | + return response |
| 206 | + |
| 207 | + |
| 208 | +def exit_gracefully(signum, frame): |
| 209 | + persistant = {} |
| 210 | + |
| 211 | + # transfer the file information into the dictionary |
| 212 | + for key in app.media_dict: |
| 213 | + entry = app.media_dict[key] |
| 214 | + entry_media = entry['media'] |
| 215 | + |
| 216 | + # stop the recording if it is in progress or paused |
| 217 | + if entry_media.current_state == Multimedia.RECORD or entry_media.current_state == Multimedia.PAUSE: |
| 218 | + entry_media.stop() |
| 219 | + |
| 220 | + persistant[key] = { |
| 221 | + 'filename': entry['filename'], |
| 222 | + 'filepath': entry['filepath'], |
| 223 | + 'status': entry_media.current_state |
| 224 | + } |
| 225 | + |
| 226 | + with open(app.storage_file, 'w') as fd: |
| 227 | + fd.write(json.dumps(persistant)) |
| 228 | + |
| 229 | + sys.exit(1) |
| 230 | + |
| 231 | + |
| 232 | +def configure(storage_file): |
| 233 | + app.record_profile = settings.profile_manager.get() |
| 234 | + app.record_config = app.record_profile.get_config('freeseer.conf', settings.FreeseerConfig, ['Global'], read_only=True) |
| 235 | + app.record_plugin_manager = PluginManager(app.record_profile) |
| 236 | + app.storage_file = os.path.join(settings.configdir, storage_file) |
| 237 | + app.next_id = 1 |
| 238 | + |
| 239 | + # restore talks from storage |
| 240 | + if os.path.isfile(app.storage_file): |
| 241 | + with open(app.storage_file) as fd: |
| 242 | + persistant = json.loads(fd.read()) |
| 243 | + |
| 244 | + app.media_dict = {} |
| 245 | + for key in persistant: |
| 246 | + new_media = Multimedia(app.record_config, app.record_plugin_manager) |
| 247 | + new_media.current_state = persistant[key]['status'] |
| 248 | + int_key = int(key) |
| 249 | + |
| 250 | + if new_media.current_state == Multimedia.NULL: |
| 251 | + filename = persistant[key]['filename'].split(".ogg")[0] |
| 252 | + success, filename = new_media.load_backend(None, filename) |
| 253 | + |
| 254 | + if success: |
| 255 | + filepath = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location |
| 256 | + app.media_dict[int_key] = { |
| 257 | + 'media': new_media, |
| 258 | + 'filename': filename, |
| 259 | + 'filepath': filepath |
| 260 | + } |
| 261 | + else: |
| 262 | + raise ServerError('Could not load multimedia backend') |
| 263 | + else: |
| 264 | + app.media_dict[int_key] = { |
| 265 | + 'media': new_media, |
| 266 | + 'filename': persistant[key]['filename'], |
| 267 | + 'filepath': persistant[key]['filepath'] |
| 268 | + } |
| 269 | + |
| 270 | + if int_key >= app.next_id: |
| 271 | + app.next_id = int_key + 1 |
| 272 | + else: |
| 273 | + app.media_dict = {} |
| 274 | + |
| 275 | + |
| 276 | +def start_server(storage_file): |
| 277 | + # setup the application so it exits gracefully |
| 278 | + signal.signal(signal.SIGINT, exit_gracefully) |
| 279 | + |
| 280 | + configure(storage_file) |
| 281 | + app.run() |
0 commit comments