# -*- coding: utf-8 -*- # Copyright (C) 2012, Sebastian Silva # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import errno import logging import sys import os import locale import subprocess from math import ceil from string import join, split import gevent from flask import Flask, request, url_for, abort, jsonify, Response,\ redirect, session, render_template, render_template_string, g from flaskext.babel import Babel, gettext as _, format_datetime from werkzeug import Headers from werkzeug import secure_filename import simplejson import tempfile from sugar_network.toolkit.http import NotFound from client import Client _BUFFER_SIZE = 1024 * 10 UPLOAD_FOLDER = tempfile.mkdtemp() WWW = None # regular browser or embedded in shell _pull_events = [] _pull_listener = None """ Initialization of local app """ app = Flask(__name__) app.secret_key = "ilovesugar" # necessary to initialize sessions babel = Babel(app) app.config['BABEL_DEFAULT_LOCALE'] = 'en' app.config['BABEL_DEFAULT_TIMEZONE'] = 'America/Lima' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER from cursors import * from dialogs import * FeedbackView.register(app) ProjectView.register(app) AboutView.register(app) def event_stream(): global _pull_events while True: for event in _pull_events: yield 'data: %s\n\n' % simplejson.dumps(_pull_events.pop(0)) gevent.sleep(1) @babel.localeselector def get_locale(): global WWW if ('localhost' in request.host) or ('127.0.0.1' in request.host): # we're probably embedded, get locale from env lang = locale.getdefaultlocale()[0].split('_')[0] logging.debug('Locale from env: %s' % lang) WWW = False else: lang = request.accept_languages.best_match(['es', 'en']) logging.debug('Locale from headers: %s' % lang) WWW = True return lang @app.template_filter('special_str') def special_str(arg=None): if not arg: return '' if isinstance(arg, basestring): # in Python 3: isinstance(arg, str) return arg else: return(arg[0]) @app.template_filter('timedelta') def timedelta(mtime): return format_datetime(mtime, _('MMMM d, yyyy')) def get_colors(): if Client.anonymous: return ('#000000', '#000000') try: import gconf except ImportError: return ('#000000', '#000000') conf = gconf.client_get_default() return conf.get_string('/desktop/sugar/user/color').split(',') def get_user(): try: import gconf except ImportError: return 'demo' conf = gconf.client_get_default() return conf.get_string('/desktop/sugar/user/nick') def get_documents_path(): """Gets the path of the DOCUMENTS folder If xdg-user-dir can not find the DOCUMENTS folder it returns $HOME, which we omit. xdg-user-dir handles localization (i.e. translation) of the filenames. Returns: Path to $HOME/DOCUMENTS or None if an error occurs Taken from jarabe/journal/model.py """ try: pipe = subprocess.Popen(['xdg-user-dir', 'DOCUMENTS'], stdout=subprocess.PIPE) documents_path = os.path.normpath(pipe.communicate()[0].strip()) if os.path.exists(documents_path) and \ os.environ.get('HOME') != documents_path: return documents_path except OSError, exception: if exception.errno != errno.ENOENT: logging.exception('Could not run xdg-user-dir') return None @app.context_processor def inject_vars(): # Here we can inject variables into every template call stroke, fill = get_colors() kwvar = { 'userid': Client.sugar_uid, 'sugar_nick' : get_user() } return dict(stroke=stroke, fill=fill, **kwvar) @app.before_request def before_request(): g.home_mount = home_mount g.network_mount = network_mount session['connected'] = network_mount.client.inline if not session['connected']: g.client = home_mount.client g.Contexts = home_mount.Contexts g.Activities = home_mount.Activities g.Artifacts = home_mount.Artifacts g.Projects = home_mount.Projects g.autocomplete_Contexts = home_mount.autocomplete_Contexts g.Questions = home_mount.Questions g.Ideas = home_mount.Ideas g.Problems = home_mount.Problems g.Solutions = home_mount.Solutions g.Comments = home_mount.Comments g.Reviews = home_mount.Reviews g.Resources = home_mount.Resources else: g.client = network_mount.client g.Contexts = network_mount.Contexts g.Activities = network_mount.Activities g.Artifacts = network_mount.Artifacts g.Projects = network_mount.Projects g.autocomplete_Contexts = network_mount.autocomplete_Contexts g.Questions = network_mount.Questions g.Ideas = network_mount.Ideas g.Problems = network_mount.Problems g.Solutions = network_mount.Solutions g.Comments = network_mount.Comments g.Reviews = network_mount.Reviews g.Resources = network_mount.Resources def incoming(event): global _pull_events _pull_events.append(event) #if event['event']=='inline' and event.get('state')=='offline': # session['connected'] = False #if event['event']=='inline' and event.get('state')=='online': # session['connected'] = True return None _pull_listener = Client.connect(incoming) @app.route('/event_source') def sse_request(): return Response( event_stream(), mimetype='text/event-stream') @app.route('/') def home(): return redirect(url_for('context_grid')) @app.route('/_stars/') def stars(context=None): """ Will make favorite and put in Home view """ if not context: return jsonify() guid = context[5:] # remove "stars-" from id favorite = request.args.get('favorite') Client.call('PUT', ['context', guid], 'favorite', content=(favorite == 'true')) # TODO Need to reset query object until supporting notifications g.Contexts._reset() return jsonify(favorite=favorite) @app.route('/_moon/') def moon(context=None): if not context: return jsonify() clone = request.args.get('clone', None) guid = context[5:] # remove "moon-" from id Client.call('PUT', ['context', guid], 'clone', clone == 'true', spawn=True) return jsonify(clone=clone) @app.route('/_toggle_connect') @app.route('/_toggle_connect/') def toggle_connect(returnto='/context'): session['connected'] = not session.get('connected') return redirect(returnto) @app.route('/context/icon/') @app.route('/context/icon/') def gen_icon(context_guid=None): try: cur_type = request.args.get('type') or session['current_type'] except KeyError: pass offset = int(request.args.get('cursor_offset') or 0) if offset and cur_type: if cur_type in ['activity']: context_cursor = g.Activities elif cur_type in ['project']: context_cursor = g.Projects else: context_cursor = g.Contexts context_guid = context_cursor[offset].guid else: context_cursor = g.client.Context return redirect(context_cursor.url(context_guid, 'icon')) @app.route('/launch/') def launch(context_guid): g.client.launch(context_guid) return redirect('/context/reviews/%s' % context_guid) @app.route('/new/resource') @app.route('/new/resource/') def new_resource(context_guid=None): """ Use Case 1: New question idea or problem """ if not context_guid: context_guid = session.get('last_context') if not context_guid: return redirect(url_for('context_grid')) offset = int(request.args.get('cursor_offset') or 0) if offset: context = g.Contexts[offset] else: context = g.client.Context(context_guid, reply=['title', 'layer']) return render_template('resource-form.html', context=context) @app.route('/artifacts/preview/') def gen_preview(guid): return redirect(g.client.Artifact.url(guid, 'preview')) @app.route('/artifacts/upload', methods=['POST']) def artifact_post(): artifact = g.client.Artifact() artifact['context'] = request.form['context'] artifact['description'] = request.form['content'] upload = request.files['artifact_file'] if upload: filename = secure_filename(upload.filename) artifact['title'] = filename artifact['type'] = 'instance' artifact.post() if filename: artifact.upload_blob('data', upload.read(), upload.content_type) return redirect('/context/artifacts/' + request.form['context']) @app.route('/artifacts/upload/to_context/') @app.route('/artifacts/upload') def artifact_upload(context=''): return render_template('upload-form.html', context=context) @app.route('/artifacts/copy/') def artifact_copy(guid): artifact = g.client.Artifact(guid, reply=['guid', 'title', 'context']) filename = artifact['title'] blob = artifact.get_blob('data') document_path = get_documents_path() with file(os.path.join(document_path, filename), 'w') as f: f.write(blob.read()) title = _('Artifact has been downloaded.') body = _('Success!\n\n' 'File %(filename)s has been copied to your Documents folder.\n' 'You can access it from the Journal.' , filename=filename) return render_template('dialog.html', title=title, body=body) @app.route('/artifacts/download/') def artifact_download(guid): if not WWW: return redirect(url_for('artifact_copy', guid=guid)) return redirect(g.client.Artifact.url(guid, 'data')) @app.route('/favorites') def favorites(): if session.get('favorites-filter'): session['favorites-filter'] = False else: session['favorites-filter'] = True return redirect(url_for('context_grid')) @app.route('/query') def autocomplete_context(): query = "title:" + request.args.get('term') r, total_pages, total, info = paginate(g.autocomplete_Contexts, query) result = [] for item in r: result.append({'value': item['guid'], 'label': item['title']}) return simplejson.dumps(result) @app.route('/search/') def search(query=None): return redirect(url_for('context_grid', query=query)) def paginate(resource, full_query, _PAGE_SIZE=6, page=1, context=None): """ This function attempts to query SN resources and return a specific page of result items. """ r = [] resource.update_filter(full_query, context=context) result = resource page_offset = _PAGE_SIZE * (page - 1) if page_offset > result.total: raise KeyError for i in range(page_offset, page_offset + _PAGE_SIZE): try: r.append(result[i]) except KeyError: pass total_pages = int(ceil(result.total / float(_PAGE_SIZE))) if total_pages == 0: info = _(u'zero results') else: info = _(u'page %(page)s of %(total)s', page=page, total=total_pages) if page > total_pages and total_pages > 0: abort(404) return r, total_pages, result.total, info @app.route('/resource/artifacts') @app.route('/resource/search/') @app.route('/resource/search/') @app.route('/resource/reviews') @app.route('/resource/reviews/') @app.route('/resource/questions') @app.route('/resource/questions/') @app.route('/resource/ideas') @app.route('/resource/ideas/') @app.route('/resource/problems') @app.route('/resource/problems/') @app.route('/resource') def resource_list(query=None): """ Feedback Resource Browser (list) """ page = request.args.get('page') if page: page = int(page) else: return redirect(request.path + "?page=1") if request.path == "/resource": resource = 'all' else: resource = split(request.path, "/")[2] if resource == 'search': resource = session.get('last_resource') or 'all' if resource == 'questions': resource_object = g.Questions resource_label = _("questions") elif resource == 'problems': resource_object = g.Problems resource_label = _("problems") elif resource == 'ideas': resource_object = g.Ideas resource_label = _("ideas") elif resource == 'reviews': resource_object = g.Reviews resource_label = _("reviews") elif resource == 'artifacts': resource_object = g.Artifacts resource_label = _("artifacts") elif resource == 'all': resource_object = g.Resources resource_label = _("resources") resource = 'all_' # avoid chop of plural form resource_type = resource[:-1] session['last_resource'] = resource session.modified = True try: r, total_pages, total, info = paginate(resource_object, query, page=page, _PAGE_SIZE=3) except KeyError: return redirect(url_for('context_resource_browser', context_guid=context_guid, page=1)) meta = _("browsing %(total)s %(resource_label)s", total=total, resource_label=resource_label) if '_pjax' in request.args: if resource == 'artifacts': template = '_artifact-list.html' elif resource == 'reviews': template = '_review-list.html' else: template = '_resource-list.html' else: template = 'resource-list.html' kwargs = {str(resource): 'button_selected'} if resource == 'artifacts': kwargs['inner_template'] = '_artifact-list.html' kwargs['artifacts_view'] = 'true' elif resource == 'reviews': kwargs['inner_template'] = '_review-list.html' else: kwargs['resource_view'] = 'true' return render_template(template, query=query, resource=resource, result=r, type='resource', page=page, info=info, total_pages=total_pages, meta=meta, resource_type=resource_type, **kwargs) @app.errorhandler(404) def page_not_found(error): title = _('Object not found.') body = _('The resource you are looking for '\ + 'is not available at the moment.\n\n'\ + 'If you are offline try connecting.'); return render_template('dialog.html', title=title, body=body) @app.errorhandler(500) def server_error(error): template = 'browser-view.html' return render_template(template, total=0, info=_('Error'), resource_type='context', query='', total_pages=0, browser_view='true', result=[], type='context', meta=_('Server error.'), page=1), 500 @app.route('/resource/contexts/') @app.route('/resource/contexts/') @app.route('/context/search/') @app.route('/context/search/') @app.route('/context') def context_grid(query=None, page=None): """ Context Grid """ try: page = int(request.args['page']) preload = request.args.get('_preload') if not ('_preload' in request.args): session['page'] = page else: session['page'] = int(request.args['_preload']) session.modified = True except KeyError: return redirect(url_for('context_grid', type=request.args.get('type'), query=query, page=session.get('page', 1))) terms = [] cur_type = request.args.get('type') or session.get('current_type') if query: terms.append(query) cur_type = 'all' if cur_type in ['activity']: context_cursor = g.Activities resource_label = _('activities') elif cur_type in ['project']: context_cursor = g.Projects resource_label = _('projects') else: # type in [None, '', 'all']: cur_type = 'all' context_cursor = g.Contexts resource_label = _('contexts') session['current_type'] = cur_type full_query = join(terms, " AND ") try: r, total_pages, total, info = paginate(context_cursor, full_query, page=page) except KeyError: if '_pjax' in request.args: return '' else: return redirect(url_for('context_grid', resource_type='context', query=query, page=1)) meta = _("browsing %(total)s %(label)s", total=total, label=resource_label) if '_pjax' in request.args: template = '_browser-grid.html' else: template = 'browser-view.html' kwargs = {str(cur_type): 'tab_selected'} return render_template(template, total=total, meta=meta, resource_type='context', query=query, total_pages=total_pages, browser_view='true', result=r, type=cur_type, info=info, page=page, **kwargs) @app.route('/user/search/') @app.route('/user/search/') @app.route('/user') def users_grid(query=None): """ Users Grid """ result = g.client.User.cursor(query) return render_template('users-grid.html', query=query, result=result, type='user') @app.route('/_comments/', methods=['DELETE']) def del_comment(resource_guid): g.client.Comment.delete(resource_guid) return "true" @app.route('/_artifacts/', methods=['DELETE']) def del_artifact(resource_guid): g.client.Artifact.delete(resource_guid) return "true" @app.route('/_comments/') def comments_browser(resource_guid=None): document = request.args['document'] g.Comments.filter(**{document: resource_guid}) result = g.Comments if not result: result = [] return render_template('_context-comment-list.html', result=result, resource_guid=resource_guid, document=document) @app.route('/article/') def project_browser(context_guid=None): inner_template = '_context-article-view.html' if '_pjax' in request.args: template = inner_template context = None else: template = 'context-view.html' context = g.client.Context(context_guid, reply=['guid', 'title', 'description', 'author', 'summary', 'layer', 'type']) try: session['last_context_title'] = context['title'] except NotFound: abort(404) session['last_context'] = context['guid'] session.modified = True return render_template(template, context=context, inner_template=inner_template, article_view=True) @app.route('/reload/') def reload(href=None): g.Solutions._reset() g.Comments._reset() g.Contexts._reset() g.Questions._reset() g.Problems._reset() g.Ideas._reset() g.Resources._reset() g.Reviews._reset() return redirect(href) @app.route('/context/reviews/') @app.route('/context/wikiwiki/') #@app.route('/context/gallery/') @app.route('/review/') def reviews_browser(resource_guid=None, review_guid=None): if review_guid: r = g.client.Review(guid=review_guid, reply=['context']) try: resource_guid = r['context'] except NotFound: abort(404) return redirect('/context/reviews/' + resource_guid) g.Reviews.filter(context=resource_guid) inner_template = '_context-review-list.html' if '_pjax' in request.args: template = inner_template context = None else: template = 'context-view.html' context = g.client.Context(resource_guid, reply=['guid', 'title', 'description', 'author', 'summary', 'layer', 'type']) try: session['last_context_title'] = context['title'] except NotFound: abort(404) session['last_context'] = context['guid'] session.modified = True kwargs = {'reviews': 'button_selected'} return render_template(template, context=context, result=g.Reviews, inner_template=inner_template, resource_label=_('reviews'), resource_type="review", **kwargs) @app.route('/question/') @app.route('/idea/') @app.route('/problem/') @app.route('/feedback/') def solution_browser(resource_guid=None): resource_type = split(request.path, "/")[1] if resource_type == 'question': resource_cursor = g.Questions elif resource_type == 'problem': resource_cursor = g.Problems elif resource_type == 'idea': resource_cursor = g.Ideas offset = int(request.args.get('cursor_offset') or 0) if offset: resource = resource_cursor[offset] else: resource = g.client.Feedback(resource_guid, reply=['guid', 'title', 'content', 'author', 'context', 'tags', 'mtime', 'type']) g.Solutions.filter(feedback=resource['guid']) inner_template = '_context-solution-list.html' if '_pjax' in request.args: template = inner_template context = None else: template = 'context-view.html' try: context = g.client.Context(resource['context'], reply=['guid', 'title', 'description', 'summary', 'author', 'layer', 'type']) except: return redirect(url_for('resource_list')) return render_template(template, context=context, result=g.Solutions, inner_template=inner_template, resource=resource, resource_type=resource_type, cursor_offset=offset) @app.route('/context/view//') @app.route('/context/view/') @app.route('/context/questions/') @app.route('/context/ideas/') @app.route('/context/problems/') @app.route('/context/artifacts/') @app.route('/context/gallery/') @app.route('/context/all/') def context_resource_browser(context_guid=None, query=None): """ Context Resources View """ page = request.args.get('page') if page: page = int(page) else: return redirect(request.path + "?page=1") resource_type = split(request.path, "/")[2] if resource_type == 'questions': resource_object = g.Questions resource_label = _("questions") elif resource_type == 'problems': resource_object = g.Problems resource_label = _("problems") elif resource_type == 'ideas': resource_object = g.Ideas resource_label = _("ideas") elif resource_type == 'artifacts' or resource_type == 'gallery': resource_type = 'artifacts' resource_object = g.Artifacts resource_label = _("artifacts") elif resource_type == 'all': resource_object = g.Resources resource_label = _("resources") try: r, total_pages, total, info = paginate(resource_object, query, page=page, _PAGE_SIZE=3, context=context_guid) except KeyError: return redirect(url_for('context_resource_browser', context_guid=context_guid, page=1)) meta = _("browsing %(total)s %(resource_label)s", total=total, resource_label=resource_label) offset = int(request.args.get('cursor_offset') or 0) if offset: context = resource_cursor[offset] else: context = g.client.Context(context_guid, reply=['guid', 'title', 'author', 'summary', 'description', 'layer', 'type']) try: session['last_context'] = context['guid'] session['last_context_title'] = context['title'] session.modified = True except NotFound: abort(404) stroke, fill = get_colors() if '_pjax' in request.args: if resource_type == 'artifacts': template = '_context-artifact-list.html' else: template = '_context-resource-list.html' _pjax = True else: template = 'context-view.html' _pjax = False kwargs = {str(resource_type): 'button_selected'} if resource_type == 'artifacts': kwargs['inner_template'] = '_context-artifact-list.html' return render_template(template, result=r, resource_type=resource_type, total_pages=total_pages, info=info, meta=meta, resource_label=resource_label, stroke=stroke, context=context, page=page, fill=fill, _pjax=_pjax, **kwargs) @app.route('/submit_edit', methods=['POST']) def edit_resource(): resource_type = request.form['resource_type'] resource_guid = request.form['edit_guid'] if resource_type == 'question': resource = g.client.Feedback(resource_guid) resource_cursor = g.Questions elif resource_type == 'idea': resource = g.client.Feedback(resource_guid) resource_cursor = g.Ideas elif resource_type == 'problem': resource = g.client.Feedback(resource_guid) resource_cursor = g.Problems elif resource_type == 'review': resource = g.client.Review(resource_guid) resource_cursor = g.Reviews elif resource_type == 'solution': resource = g.client.Solution(resource_guid) resource_cursor = g.Solutions if request.form.get('title'): resource['title'] = request.form['title'] if request.form.get('content'): resource['content'] = request.form['content'] resource.post() resource_cursor._reset() return redirect(request.form.get('href')) @app.route('/submit_resource', methods=['POST']) def submit_resource(): for r_type in ('question', 'idea', 'problem'): if request.form.get(r_type, None): resource_type = r_type if resource_type == 'question': resource_cursor = g.Questions elif resource_type == 'idea': resource_cursor = g.Ideas elif resource_type == 'problem': resource_cursor = g.Problems resource = g.client.Feedback() resource['content'] = request.form['content'] resource['title'] = request.form['title'] resource['context'] = request.form['guid'] resource['type'] = resource_type if resource['title']: resource.post() resource_cursor._reset() return redirect('/context/%ss/%s' % (resource_type, resource['context'])) @app.route('/update_context', methods=['POST']) def update_context(): context = g.client.Context(request.form['edit_guid']) context['title'] = request.form['title'] context['layer'] = ['public', 'pilot'] context['summary'] = request.form['summary'] context['description'] = request.form['content'] if context['title'] and context['description'] and context['summary']: context.post() else: abort(500) return redirect('/article/%s' % request.form['edit_guid']) @app.route('/submit_context', methods=['POST']) def new_context(): context = g.client.Context() context['type'] = ['project'] context['title'] = request.form['title'] context['layer'] = ['public', 'pilot'] context['summary'] = request.form['summary'] context['description'] = request.form['content'] if context['title'] and context['description'] and context['summary']: context.post() g.Contexts._reset() else: abort(500) return redirect(url_for('project_browser', context_guid=context['guid'])) @app.route('/submit_solution', methods=['POST']) def new_solution(): solution = g.client.Solution() solution['content'] = request.form['solution'] solution['feedback'] = request.form['resource_guid'] if solution['content']: solution.post() g.Solutions._reset() return redirect('/%s/%s' % (request.form['resource_type'], solution['feedback'])) @app.route('/submit_review', methods=['POST']) def new_review(): review = g.client.Review() review['content'] = request.form['review'] review['title'] = '' review['rating'] = 0 context = review['context'] = request.form['resource_guid'] if review['content']: review.post() g.Reviews._reset() return redirect('/context/reviews/%s' % context) @app.route('/submit_comment', methods=['POST']) def new_comment(): document = request.form['document'] comment = g.client.Comment() comment['message'] = request.form['comment'] comment[document] = request.form['resource_guid'] if comment['message']: comment.post() g.Comments._reset() return redirect('_comments/%s?resource_type=%s&document=%s' % (comment[document], request.form['resource_type'], document)) @app.route('/_shutdown') def shutdown_server(): func = request.environ.get('werkzeug.server.shutdown') if func is None: raise RuntimeError('Not running with the Werkzeug Server') func() return 'Goodbye' @app.route('/_debug') def debug(): raise Warning('All your base are belong to us.') return 'Take off every Zig' if __name__ == "__main__": host = '0.0.0.0' # '0.0.0.0' for all try: port = int(sys.argv[1]) except IndexError: port = 5000 app.debug = True from gevent.wsgi import WSGIServer http_server = WSGIServer((host, port), app) http_server.serve_forever() #app.run(host=host, port=port)