def message_search_api(): g.parser.add_argument('q', type=bounded_str, location='args') args = strict_parse_args(g.parser, request.args) if request.method == 'GET': if not args['q']: err_string = ('GET HTTP method must include query' ' url parameter') g.log.error(err_string) return err(400, err_string) search_client = get_search_client(g.namespace.account) results = search_client.search_messages(g.db_session, args['q']) else: data = request.get_json(force=True) query = data.get('query') validate_search_query(query) sort = data.get('sort') validate_search_sort(sort) try: search_engine = NamespaceSearchEngine(g.namespace_public_id) results = search_engine.messages.search(query=query, sort=sort, max_results=args.limit, offset=args.offset) except SearchEngineError as e: g.log.error('Search error: {0}'.format(e)) return err(501, 'Search error') return g.encoder.jsonify(results)
def thread_api_update(public_id): try: valid_public_id(public_id) thread = g.db_session.query(Thread).filter( Thread.public_id == public_id, Thread.namespace_id == g.namespace.id).one() except NoResultFound: raise NotFoundError("Couldn't find thread `{0}` ".format(public_id)) data = request.get_json(force=True) if not set(data).issubset({'add_tags', 'remove_tags', 'version'}): raise InputError('Can only add or remove tags from thread.') if (data.get('version') is not None and data.get('version') != thread.version): raise ConflictError('Thread {} has been updated to version {}'. format(thread.public_id, thread.version)) removals = data.get('remove_tags', []) for tag_identifier in removals: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: raise NotFoundError("Couldn't find tag {}".format(tag_identifier)) if not tag.user_removable: raise InputError('Cannot remove read-only tag {}'. format(tag_identifier)) try: thread.remove_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) additions = data.get('add_tags', []) for tag_identifier in additions: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: raise NotFoundError("Couldn't find tag {}".format(tag_identifier)) if not tag.user_addable: raise InputError('Cannot add read-only tag {}'. format(tag_identifier)) try: thread.apply_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) g.db_session.commit() return g.encoder.jsonify(thread)
def thread_api_update(public_id): try: valid_public_id(public_id) thread = g.db_session.query(Thread).filter( Thread.public_id == public_id, Thread.namespace_id == g.namespace.id).one() except NoResultFound: raise NotFoundError("Couldn't find thread `{0}` ".format(public_id)) data = request.get_json(force=True) if not set(data).issubset({'add_tags', 'remove_tags', 'version'}): raise InputError('Can only add or remove tags from thread.') if (data.get('version') is not None and data.get('version') != thread.version): raise ConflictError('Thread {} has been updated to version {}'. format(thread.public_id, thread.version)) removals = data.get('remove_tags', []) for tag_identifier in removals: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: raise NotFoundError("Couldn't find tag {}".format(tag_identifier)) if not tag.user_removable: raise InputError('Cannot remove tag {}'.format(tag_identifier)) try: thread.remove_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) additions = data.get('add_tags', []) for tag_identifier in additions: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: raise NotFoundError("Couldn't find tag {}".format(tag_identifier)) if not tag.user_addable: raise InputError('Cannot remove tag {}'.format(tag_identifier)) try: thread.apply_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) g.db_session.commit() return g.encoder.jsonify(thread)
def draft_update_api(public_id): data = request.get_json(force=True) original_draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) try: draft = sendmail.update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, tags) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def draft_update_api(public_id): data = request.get_json(force=True) original_draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') from_addr = get_recipients(data.get('from_addr'), 'from_addr') reply_to = get_recipients(data.get('reply_to'), 'reply_to') if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) try: draft = update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, from_addr, reply_to, tags) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def account(account_id): try: account = g.db_session.query(Account).get(account_id) except NoResultFound: return err(404, 'No account with id `{0}`'.format(account_id)) if 'action' in request.args: action = request.args.get('action', None) if action == 'stop': if account.sync_enabled: account.stop_sync() g.db_session.add(account) g.db_session.commit() elif action == 'start': account.start_sync() g.db_session.add(account) g.db_session.commit() if account: folders_info = [ foldersyncstatus.metrics for foldersyncstatus in account.foldersyncstatuses ] sync_status = account.sync_status else: folders_info = [] sync_status = {} return json.dumps({ "account": sync_status, "folders": folders_info }, cls=DateTimeJSONEncoder)
def tag_update_api(public_id): try: valid_public_id(public_id) tag = g.db_session.query(Tag).filter( Tag.public_id == public_id, Tag.namespace_id == g.namespace.id).one() except NoResultFound: raise NotFoundError('No tag found') data = request.get_json(force=True) if not ('name' in data.keys() and isinstance(data['name'], basestring)): raise InputError('Malformed tag update request') if 'namespace_id' in data.keys(): ns_id = data['namespace_id'] valid_public_id(ns_id) if ns_id != g.namespace.public_id: raise InputError('Cannot change the namespace on a tag.') if not tag.user_created: raise InputError('Cannot modify tag {}'.format(public_id)) # Lowercase tag name, regardless of input casing. new_name = data['name'].lower() if new_name != tag.name: # short-circuit rename to same value if not Tag.name_available(new_name, g.namespace.id, g.db_session): return err(409, 'Tag name already used') tag.name = new_name g.db_session.commit() return g.encoder.jsonify(tag)
def account(account_id): try: account = g.db_session.query(Account).get(account_id) except NoResultFound: return err(404, 'No account with id `{0}`'.format(account_id)) if 'action' in request.args: action = request.args.get('action', None) if action == 'stop': if account.sync_enabled: account.stop_sync() g.db_session.add(account) g.db_session.commit() elif action == 'start': account.start_sync() g.db_session.add(account) g.db_session.commit() if account: folders_info = [foldersyncstatus.metrics for foldersyncstatus in account.foldersyncstatuses] sync_status = account.sync_status else: folders_info = [] sync_status = {} return json.dumps({"account": sync_status, "folders": folders_info}, cls=DateTimeJSONEncoder)
def send_draft_copy(account, draft, custom_body, recipient): """ Sends a copy of this draft to the recipient, using the specified body rather that the one on the draft object, and not marking the draft as sent. Used within multi-send to send messages to individual recipients with customized bodies. """ # Create the response to send on success by serlializing the draft. Before # serializing, we temporarily swap in the new custom body (which the # recipient will get and which should be returned in this response) in # place of the existing body (which we still need to retain in the draft # for when it's saved to the sent folder). We replace the existing body # after serialization is done. original_body = draft.body draft.body = custom_body response_on_success = APIEncoder().jsonify(draft) draft.body = original_body # Now send the draft to the specified recipient. The send_custom method # will write the custom body into the message in place of the one in the # draft. try: sendmail_client = get_sendmail_client(account) sendmail_client.send_custom(draft, custom_body, [recipient]) except SendMailException as exc: kwargs = {} if exc.failures: kwargs['failures'] = exc.failures if exc.server_error: kwargs['server_error'] = exc.server_error return err(exc.http_code, exc.message, **kwargs) return response_on_success
def send_draft_copy(account, draft, custom_body, recipient): """ Sends a copy of this draft to the recipient, using the specified body rather that the one on the draft object, and not marking the draft as sent. Used within multi-send to send messages to individual recipients with customized bodies. """ # Create the response to send on success by serlializing the draft. After # serializing, we replace the new custom body (which the recipient will get # and which should be returned in this response) in place of the existing # body (which we still need to retain in the draft for when it's saved to # the sent folder). response_on_success = encode(draft) response_on_success["body"] = custom_body response_on_success = APIEncoder().jsonify(response_on_success) # Now send the draft to the specified recipient. The send_custom method # will write the custom body into the message in place of the one in the # draft. try: sendmail_client = get_sendmail_client(account) sendmail_client.send_custom(draft, custom_body, [recipient]) except SendMailException as exc: kwargs = {} if exc.failures: kwargs["failures"] = exc.failures if exc.server_error: kwargs["server_error"] = exc.server_error return err(exc.http_code, exc.args[0], **kwargs) return response_on_success
def sync_deltas(): g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) g.parser.add_argument('exclude_types', type=valid_delta_object_types, location='args') g.parser.add_argument('include_types', type=valid_delta_object_types, location='args') g.parser.add_argument('timeout', type=int, default=LONG_POLL_REQUEST_TIMEOUT, location='args') # TODO(emfree): should support `expand` parameter in delta endpoints. args = strict_parse_args(g.parser, request.args) exclude_types = args.get('exclude_types') include_types = args.get('include_types') cursor = args['cursor'] timeout = args['timeout'] if include_types and exclude_types: return err(400, "Invalid Request. Cannot specify both include_types" "and exclude_types") if cursor == '0': start_pointer = 0 else: try: start_pointer, = g.db_session.query(Transaction.id). \ filter(Transaction.public_id == cursor, Transaction.namespace_id == g.namespace.id).one() except NoResultFound: raise InputError('Invalid cursor parameter') # The client wants us to wait until there are changes g.db_session.close() # hack to close the flask session poll_interval = 1 start_time = time.time() while time.time() - start_time < timeout: with session_scope() as db_session: deltas, _ = delta_sync.format_transactions_after_pointer( g.namespace, start_pointer, db_session, args['limit'], exclude_types, include_types) response = { 'cursor_start': cursor, 'deltas': deltas, } if deltas: response['cursor_end'] = deltas[-1]['cursor'] return g.encoder.jsonify(response) # No changes. perhaps wait elif '/delta/longpoll' in request.url_rule.rule: gevent.sleep(poll_interval) else: # Return immediately response['cursor_end'] = cursor return g.encoder.jsonify(response) # If nothing happens until timeout, just return the end of the cursor response['cursor_end'] = cursor return g.encoder.jsonify(response)
def send_draft(account, draft, db_session, schedule_remote_delete): """Send the draft with id = `draft_id`.""" try: sendmail_client = get_sendmail_client(account) sendmail_client.send(draft) except SendMailException as exc: if exc.failures: return err(exc.http_code, exc.message, failures=exc.failures) return err(exc.http_code, exc.message) # We want to return success to the API client if the message was sent, even # if there are errors in post-send updating. Otherwise the client may think # the send has failed. So wrap the rest of the work in try/except. try: if account.provider == 'icloud': # Special case because iCloud doesn't save sent messages. schedule_action('save_sent_email', draft, draft.namespace.id, db_session) if schedule_remote_delete: schedule_action('delete_draft', draft, draft.namespace.id, db_session, inbox_uid=draft.inbox_uid, message_id_header=draft.message_id_header) # Update message draft.is_sent = True draft.is_draft = False draft.received_date = datetime.utcnow() # Update thread sent_tag = account.namespace.tags['sent'] draft_tag = account.namespace.tags['drafts'] thread = draft.thread thread.apply_tag(sent_tag) # Remove the drafts tag from the thread if there are no more drafts. if not draft.thread.drafts: thread.remove_tag(draft_tag) thread.update_from_message(None, draft) except Exception as e: log.error('Error in post-send processing', error=e, exc_info=True) return APIEncoder().jsonify(draft)
def _for_account(account_id): try: account = g.db_session.query(Account).get(account_id) except NoResultFound: return err(404, 'No account with id `{0}`'.format(account_id)) folders_info = [foldersyncstatus.metrics for foldersyncstatus in account.foldersyncstatuses] return json.dumps(folders_info, cls=DateTimeJSONEncoder)
def draft_create_api(): data = request.get_json(force=True) try: draft = create_draft(data, g.namespace, g.db_session, syncback=True) g.db_session.add(draft) g.db_session.commit() except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def for_account(account_id): try: account = g.db_session.query(Account).get(account_id) except NoResultFound: return err(404, 'No account with id `{0}`'.format(account_id)) acct_info = account.sync_status template = 'for_account.html' if account.provider != 'eas' else \ 'for_eas_account.html' return render_template(template, account=acct_info)
def draft_delete_api(public_id): data = request.get_json(force=True) # Validate draft id, version, etc. draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) try: result = delete_draft(g.db_session, g.namespace.account, draft) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(result)
def stream_changes(): g.parser.add_argument('timeout', type=float, location='args') g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) g.parser.add_argument('exclude_types', type=valid_delta_object_types, location='args') g.parser.add_argument('include_types', type=valid_delta_object_types, location='args') args = strict_parse_args(g.parser, request.args) timeout = args['timeout'] or 1800 transaction_pointer = None cursor = args['cursor'] exclude_types = args.get('exclude_types') include_types = args.get('include_types') if include_types and exclude_types: return err( 400, "Invalid Request. Cannot specify both include_types" "and exclude_types") if cursor == '0': transaction_pointer = 0 else: query_result = g.db_session.query(Transaction.id).filter( Transaction.namespace_id == g.namespace.id, Transaction.public_id == cursor).first() if query_result is None: raise InputError('Invalid cursor {}'.format(args['cursor'])) transaction_pointer = query_result[0] # Hack to not keep a database session open for the entire (long) request # duration. g.db_session.expunge(g.namespace) g.db_session.close() # TODO make transaction log support the `expand` feature generator = delta_sync.streaming_change_generator( g.namespace, transaction_pointer=transaction_pointer, poll_interval=1, timeout=timeout, exclude_types=exclude_types, include_types=include_types) return Response(generator, mimetype='text/event-stream')
def send_raw_mime(account, db_session, msg): # Prepare a response so that we can immediately return it on success, and # not potentially have queries fail after sending. response_on_success = APIEncoder().jsonify(msg) try: sendmail_client = get_sendmail_client(account) sendmail_client.send_raw(msg) except SendMailException as exc: kwargs = {} if exc.failures: kwargs['failures'] = exc.failures if exc.server_error: kwargs['server_error'] = exc.server_error return err(exc.http_code, exc.message, **kwargs) return response_on_success
def message_search_api(): args = strict_parse_args(g.parser, request.args) data = request.get_json(force=True) query = data.get('query') validate_search_query(query) try: search_engine = NamespaceSearchEngine(g.namespace_public_id) results = search_engine.messages.search(query=query, max_results=args.limit, offset=args.offset) except SearchEngineError as e: g.log.error('Search error: {0}'.format(e)) return err(501, 'Search error') return g.encoder.jsonify(results)
def tag_create_api(): data = request.get_json(force=True) if not ('name' in data.keys() and isinstance(data['name'], basestring)): raise InputError('Malformed tag request') if 'namespace_id' in data.keys(): ns_id = data['namespace_id'] valid_public_id(ns_id) if ns_id != g.namespace.id: raise InputError('Cannot change the namespace on a tag.') # Lowercase tag name, regardless of input casing. tag_name = data['name'].lower() if not Tag.name_available(tag_name, g.namespace.id, g.db_session): return err(409, 'Tag name not available') if len(tag_name) > MAX_INDEXABLE_LENGTH: raise InputError('Tag name is too long.') tag = Tag(name=tag_name, namespace=g.namespace, user_created=True) g.db_session.commit() return g.encoder.jsonify(tag)
def auth(): """ Check for account ID on all non-root URLS """ if request.path in ('/accounts', '/accounts/', '/', '/n', '/n/') \ or request.path.startswith('/w/'): return if request.path.startswith('/n/'): ns_parts = filter(None, request.path.split('/')) namespace_public_id = ns_parts[1] valid_public_id(namespace_public_id) with global_session_scope() as db_session: try: namespace = db_session.query(Namespace) \ .filter(Namespace.public_id == namespace_public_id).one() g.namespace_id = namespace.id except NoResultFound: return err(404, "Unknown namespace ID") else: if not request.authorization or not request.authorization.username: return make_response(("Could not verify access credential.", 401, { 'WWW-Authenticate': 'Basic realm="API ' 'Access Token Required"' })) namespace_public_id = request.authorization.username with global_session_scope() as db_session: try: valid_public_id(namespace_public_id) namespace = db_session.query(Namespace) \ .filter(Namespace.public_id == namespace_public_id).one() g.namespace_id = namespace.id g.account_id = namespace.account.id except NoResultFound: return make_response( ("Could not verify access credential.", 401, { 'WWW-Authenticate': 'Basic realm="API ' 'Access Token Required"' }))
def send_draft(account, draft, db_session): """Send the draft with id = `draft_id`.""" # Update message state and prepare a response so that we can immediately # return it on success, and not potentially have queries fail after # sending. Note that changes are flushed here, but committed in the API's # after_request handler only on 200 OK (hence only if sending succeeds). update_draft_on_send(account, draft, db_session) response_on_success = APIEncoder().jsonify(draft) try: sendmail_client = get_sendmail_client(account) sendmail_client.send(draft) except SendMailException as exc: kwargs = {} if exc.failures: kwargs['failures'] = exc.failures if exc.server_error: kwargs['server_error'] = exc.server_error return err(exc.http_code, exc.message, **kwargs) return response_on_success
def auth(): """ Check for account ID on all non-root URLS """ if request.path in ("/accounts", "/accounts/", "/", "/n", "/n/") or request.path.startswith("/w/"): return if request.path.startswith("/n/"): ns_parts = filter(None, request.path.split("/")) namespace_public_id = ns_parts[1] valid_public_id(namespace_public_id) with session_scope() as db_session: try: namespace = db_session.query(Namespace).filter(Namespace.public_id == namespace_public_id).one() g.namespace_public_id = namespace.public_id except NoResultFound: return err(404, "Unknown namespace ID") else: if not request.authorization or not request.authorization.username: return make_response( ( "Could not verify access credential.", 401, {"WWW-Authenticate": 'Basic realm="API ' 'Access Token Required"'}, ) ) g.namespace_public_id = request.authorization.username with session_scope() as db_session: try: valid_public_id(g.namespace_public_id) namespace = db_session.query(Namespace).filter(Namespace.public_id == g.namespace_public_id).one() except NoResultFound: return make_response( ( "Could not verify access credential.", 401, {"WWW-Authenticate": 'Basic realm="API ' 'Access Token Required"'}, ) )
def draft_create_api(): data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get('thread_id'), g.namespace.id, g.db_session) try: draft = sendmail.create_draft(g.db_session, g.namespace.account, to, subject, body, files, cc, bcc, tags, replyto_thread) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def calendar_create_api(): data = request.get_json(force=True) if 'name' not in data: raise InputError("Calendar must have a name.") name = data['name'] existing = g.db_session.query(Calendar).filter( Calendar.name == name, Calendar.namespace_id == g.namespace.id).first() if existing: return err(409, "A calendar already exists with name '{}'.".format(name)) description = data.get('description', None) cal_create = events.crud.create_calendar new_calendar = cal_create(g.namespace, g.db_session, name, description) return g.encoder.jsonify(new_calendar)
def calendar_create_api(): data = request.get_json(force=True) if 'name' not in data: raise InputError("Calendar must have a name.") name = data['name'] existing = g.db_session.query(Calendar).filter( Calendar.name == name, Calendar.namespace_id == g.namespace.id).first() if existing: return err(409, "A calendar already exists with name '{}'.". format(name)) description = data.get('description', None) cal_create = events.crud.create_calendar new_calendar = cal_create(g.namespace, g.db_session, name, description) return g.encoder.jsonify(new_calendar)
def auth(): """ Check for account ID on all non-root URLS """ if request.path in ('/accounts', '/accounts/', '/', '/n', '/n/') \ or request.path.startswith('/w/'): return if request.path.startswith('/n/'): ns_parts = filter(None, request.path.split('/')) namespace_public_id = ns_parts[1] valid_public_id(namespace_public_id) with global_session_scope() as db_session: try: namespace = db_session.query(Namespace) \ .filter(Namespace.public_id == namespace_public_id).one() g.namespace_id = namespace.id except NoResultFound: return err(404, "Unknown namespace ID") else: if not request.authorization or not request.authorization.username: return make_response(( "Could not verify access credential.", 401, {'WWW-Authenticate': 'Basic realm="API ' 'Access Token Required"'})) namespace_public_id = request.authorization.username with global_session_scope() as db_session: try: valid_public_id(namespace_public_id) namespace = db_session.query(Namespace) \ .filter(Namespace.public_id == namespace_public_id).one() g.namespace_id = namespace.id g.account_id = namespace.account.id except NoResultFound: return make_response(( "Could not verify access credential.", 401, {'WWW-Authenticate': 'Basic realm="API ' 'Access Token Required"'}))
def stream_changes(): g.parser.add_argument('timeout', type=float, location='args') g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) g.parser.add_argument('exclude_types', type=valid_delta_object_types, location='args') g.parser.add_argument('include_types', type=valid_delta_object_types, location='args') args = strict_parse_args(g.parser, request.args) timeout = args['timeout'] or 1800 transaction_pointer = None cursor = args['cursor'] exclude_types = args.get('exclude_types') include_types = args.get('include_types') if include_types and exclude_types: return err(400, "Invalid Request. Cannot specify both include_types" "and exclude_types") if cursor == '0': transaction_pointer = 0 else: query_result = g.db_session.query(Transaction.id).filter( Transaction.namespace_id == g.namespace.id, Transaction.public_id == cursor).first() if query_result is None: raise InputError('Invalid cursor {}'.format(args['cursor'])) transaction_pointer = query_result[0] # Hack to not keep a database session open for the entire (long) request # duration. g.db_session.expunge(g.namespace) g.db_session.close() # TODO make transaction log support the `expand` feature generator = delta_sync.streaming_change_generator( g.namespace, transaction_pointer=transaction_pointer, poll_interval=1, timeout=timeout, exclude_types=exclude_types, include_types=include_types) return Response(generator, mimetype='text/event-stream')
def account(account_id): try: account = g.db_session.query(Account).get(account_id) except NoResultFound: return err(404, 'No account with id `{0}`'.format(account_id)) if 'action' in request.args: root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..') bin_path = os.path.abspath(os.path.join(root_path, 'bin')) inbox_sync = os.path.join(bin_path, 'inbox-sync') action = request.args.get('action', None) if action == 'stop': if account.sync_enabled: print "stopping: ", account_id account.stop_sync() elif action == 'start': print "starting: ", account_id account.start_sync(platform.node()) if account: folders_info = [ foldersyncstatus.metrics for foldersyncstatus in account.foldersyncstatuses ] sync_status = account.sync_status else: folders_info = [] sync_status = {} return json.dumps({ "account": sync_status, "folders": folders_info }, cls=DateTimeJSONEncoder)