From 0f409fdb126c2dedd91d93bcd57b76fe98cbcc55 Mon Sep 17 00:00:00 2001
From: Philip ABBET <philip.abbet@idiap.ch>
Date: Fri, 18 Nov 2016 16:49:44 +0100
Subject: [PATCH] [website] Allows the upload of a shared library for a binary
 algorithm

---
 beat/web/algorithms/api.py                    | 10 ++-
 .../static/algorithms/css/editor.css          | 13 ++++
 .../templates/algorithms/edition.html         | 68 ++++++++++++++++---
 .../templates/algorithms/panels/editor.html   | 11 +++
 .../algorithms/templates/algorithms/view.html |  2 +-
 beat/web/algorithms/views.py                  | 27 +++++---
 beat/web/code/models.py                       | 14 ++++
 beat/web/common/texts.py                      |  1 +
 buildout.cfg                                  |  1 +
 9 files changed, 124 insertions(+), 23 deletions(-)
 mode change 100644 => 100755 beat/web/algorithms/views.py
 mode change 100644 => 100755 beat/web/common/texts.py

diff --git a/beat/web/algorithms/api.py b/beat/web/algorithms/api.py
index c40350671..11a1e03fb 100755
--- a/beat/web/algorithms/api.py
+++ b/beat/web/algorithms/api.py
@@ -181,8 +181,12 @@ def binary(request, author_name, object_name, version=None):
             return HttpResponseBadRequest()
 
         file = request.FILES['binary']
-        with open(os.path.join(settings.ALGORITHMS_ROOT, algorithm.source_code_filename()), 'wb') as dest:
-            for chunk in file.chunks():
-                dest.write(chunk)
+
+        binary_data = ''
+        for chunk in file.chunks():
+            binary_data += chunk
+
+        algorithm.source_code = binary_data
+        algorithm.save()
 
         return HttpResponse(status=204)
diff --git a/beat/web/algorithms/static/algorithms/css/editor.css b/beat/web/algorithms/static/algorithms/css/editor.css
index 6a6c48aca..a643f3dd6 100644
--- a/beat/web/algorithms/static/algorithms/css/editor.css
+++ b/beat/web/algorithms/static/algorithms/css/editor.css
@@ -292,3 +292,16 @@ div.documentation_editor hr.separator
     color: #C0C0C0;
     background-color: #C0C0C0;
 }
+
+
+div#binary_file_selector span.binary_file_button
+{
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+
+div#binary_file_selector span.filename
+{
+    font-style: italic;
+}
diff --git a/beat/web/algorithms/templates/algorithms/edition.html b/beat/web/algorithms/templates/algorithms/edition.html
index 1af55e4be..47fb65d44 100644
--- a/beat/web/algorithms/templates/algorithms/edition.html
+++ b/beat/web/algorithms/templates/algorithms/edition.html
@@ -36,6 +36,7 @@
 {{ block.super }}
 <link rel="stylesheet" href="{% fingerprint "algorithms/css/editor.css" %}" type="text/css" media="screen" />
 <link rel="stylesheet" href="{% fingerprint "ui/css/smart-selector.css" %}" type="text/css" media="screen" />
+<link rel="stylesheet" href="{% fingerprint "blueimp-file-upload/css/jquery.fileupload.css" %}" type="text/css" media="screen" />
 {% code_editor_css %}
 {% endblock %}
 
@@ -46,6 +47,8 @@
 
 <script src="{% fingerprint "algorithms/js/editor.js" %}" type="text/javascript" charset="utf-8"></script>
 <script src="{% fingerprint "ui/js/smartselector.js" %}" type="text/javascript" charset="utf-8"></script>
+<script src="{% fingerprint "jquery-ui/ui/minified/jquery-ui.min.js" %}" type="text/javascript" charset="utf-8"></script>
+<script src="{% fingerprint "blueimp-file-upload/js/jquery.fileupload.js" %}" type="text/javascript" charset="utf-8"></script>
 {% code_editor_scripts "python" %}
 
 <script type="text/javascript">
@@ -73,9 +76,10 @@ function setupEditor(algorithm, dataformats, libraries)
 {
   $('.contribution_editor').show();
 
+{% if not binary %}
   // Source code editor
   var source_code_editor = new beat.contributions.editor.SourceCodeEditor('source_code');
-
+{% endif %}
 
   // Options
   var declaration = JSON.parse('{{ declaration|escapejs }}');
@@ -145,6 +149,7 @@ function setupEditor(algorithm, dataformats, libraries)
       );
 
 
+{% if not binary %}
   // Libraries editor
   if (declaration.uses === undefined)
     declaration.uses = {};
@@ -153,7 +158,7 @@ function setupEditor(algorithm, dataformats, libraries)
       'libraries_editor', declaration.uses,
       libraries, smart_selector
       );
-
+{% endif %}
 
   // Analyzer checkbox handling
   checkbox_analyzer.change(function() {
@@ -172,11 +177,14 @@ function setupEditor(algorithm, dataformats, libraries)
       results_panel.hide();
     }
 
+{% if not binary %}
     source_code_editor.changeProcessMethod(checkbox_analyzer[0].checked);
+{% endif %}
     inputs_editor.setAnalyzer(checkbox_analyzer[0].checked);
   });
 
 
+{% if not binary %}
   // Language radio buttons handling
   var language_python_selector = $('#language_python');
   var language_cxx_selector = $('#language_cxx');
@@ -191,6 +199,21 @@ function setupEditor(algorithm, dataformats, libraries)
     $('#source_code_editor').hide();
     $('#libraries-editor-panel').hide();
   });
+{% endif %}
+
+
+{% if binary %}
+  $('#shared_library').fileupload({
+      url: '{% url "api_algorithms:binary" algorithm_author algorithm_name algorithm_version %}',
+      add: function (e, data) {
+          $('.button_save')[0].shared_library = data;
+          $('#binary_file_name').text(data.files[0].name);
+      },
+      done: function (e, data) {
+          window.location = '{% url "algorithms:view" algorithm_author algorithm_name algorithm_version %}';
+      }
+  });
+{% endif %}
 
 
   // Save button checkbox handling
@@ -208,14 +231,16 @@ function setupEditor(algorithm, dataformats, libraries)
     {% endif %}
     {% endif %}
 
-    var declaration = {
-      language: 'python',
-    };
+    var declaration = {};
 
+{% if not edition %}
     if (language_python_selector[0].checked)
       declaration.language = 'python';
     else
       declaration.language = 'cxx';
+{% else %}
+    declaration.language = '{{ algorithm_language }}';
+{% endif %}
 
     if (!checkbox_analyzer[0].checked)
     {
@@ -236,13 +261,14 @@ function setupEditor(algorithm, dataformats, libraries)
     if (declaration.parameters === null)
       return false;
 
+{% if not binary %}
     if (language_python_selector[0].checked)
     {
       declaration.uses = libraries_panel.getLibraries(displayErrors);
       if (declaration.uses === null)
         return false;
     }
-
+{% endif %}
 
     var data = {
       {% if not edition %}
@@ -261,8 +287,10 @@ function setupEditor(algorithm, dataformats, libraries)
       {% endif %}
     };
 
+{% if not binary %}
     if (language_python_selector[0].checked)
       data.code = source_code_editor.getSourceCode();
+{% endif %}
 
 
     $.ajax({
@@ -277,10 +305,14 @@ function setupEditor(algorithm, dataformats, libraries)
       contentType: "application/json; charset=utf-8",
       dataType: "json",
       success: function(data) {
-        {% if edition %}
+        {% if not binary %}
+          {% if edition %}
         window.location = '{% url "algorithms:view" algorithm_author algorithm_name algorithm_version %}';
-        {% else %}
+          {% else %}
         window.location = data.object_view;
+          {% endif %}
+        {% else %}
+          $('.button_save')[0].shared_library.submit();
         {% endif %}
       },
       error: function(jqXHR, textStatus, errorThrown) {
@@ -489,6 +521,7 @@ function setupEditor(algorithm, dataformats, libraries)
 
 
       {# libraries editor panel #}
+    {% if not binary %}
       <div id="libraries-editor-panel" class="panel panel-default contribution_editor " style="display:none;">
         <div class="panel-heading" role="tab" id="library-editor-1">
           <h4 class="panel-title">
@@ -519,11 +552,13 @@ function setupEditor(algorithm, dataformats, libraries)
           </div>{# panel-body #}
         </div>{# panel-collapse #}
       </div>{# panel #}
+    {% endif %}
 
     </div>{# panel-group #}
   </div>{# row #}
 </div>
 
+{% if not binary %}
 <div id="source_code_editor" class="row">
   <div class="col-sm-offset-1 col-sm-10 contribution_editor" style="display: none;">
     <label for="source_code">Source code:</label>
@@ -531,6 +566,23 @@ function setupEditor(algorithm, dataformats, libraries)
     <p class="help">{{ messages.code|safe }}</p>
   </div>
 </div>
+{% else %}
+<div id="binary_file_selector" class="row">
+  <div class="col-sm-offset-1 col-sm-10 contribution_editor" style="display: none;">
+    <div class="alert alert-info">
+      <i class="fa fa-info"></i> <span>This algorithm is implemented in <strong>{{ algorithm_language_name }}</strong>, compiled as a <strong>shared library</strong>.</span>
+    </div>
+    <label for="shared_library">Shared library:</label>
+    <span class="btn btn-success fileinput-button binary_file_button">
+        <i class="glyphicon glyphicon-plus"></i>
+        <span>Select file...</span>
+        <input id="shared_library" type="file" name="binary" />
+    </span>
+    <span id="binary_file_name" class="filename"></span>
+    <p class="help">{{ messages.shared_library|safe }}</p>
+  </div>
+</div>
+{% endif %}
 
 {% smart_selector "smart_selector" %}
 
diff --git a/beat/web/algorithms/templates/algorithms/panels/editor.html b/beat/web/algorithms/templates/algorithms/panels/editor.html
index 711ddd31b..14ebdc0f1 100644
--- a/beat/web/algorithms/templates/algorithms/panels/editor.html
+++ b/beat/web/algorithms/templates/algorithms/panels/editor.html
@@ -231,6 +231,17 @@
     {% if open_source and not object.is_binary %}
     <textarea class="form-control" id="code-display">{{ object.source_code_file.read }}</textarea>
     <p class="help-block">{{ texts.code|safe }}</p>
+    {% elif object.is_binary %}
+      {% if object.valid %}
+    <div class="alert alert-info">
+      <i class="fa fa-info"></i> <span>This algorithm is implemented in <strong>{{ object.language_fullname }}</strong>, compiled as a <strong>shared library</strong>.</span>
+      <a id="btn-download-binary" class="btn btn-success btn-sm" href="{% url "api_algorithms:binary" object.author.username object.name object.version %}"><i class="fa fa-download fa-lg"></i> Download</a>
+    </div>
+      {% else %}
+    <div class="alert alert-warning">
+      <i class="fa fa-warning"></i> This algorithm must be implemented in <strong>{{ object.language_fullname }}</strong>, compiled as a <strong>shared library</strong> and uploaded to the platform.
+    </div>
+      {% endif %}
     {% else %}
     <div class="alert alert-warning">
       <i class="fa fa-warning"></i> This algorithm is only usable to you. Its code was <strong>not</strong> shared.
diff --git a/beat/web/algorithms/templates/algorithms/view.html b/beat/web/algorithms/templates/algorithms/view.html
index cde3bb969..a791b7748 100644
--- a/beat/web/algorithms/templates/algorithms/view.html
+++ b/beat/web/algorithms/templates/algorithms/view.html
@@ -131,7 +131,7 @@
             {% for key, value in execinfo %}<li class="list-group-item"><a title="Click to view" data-toggle="tooltip" data-placement="top" href="{% url 'backend:view-environment' key.name key.version %}">{{ key.fullname }}</a> <span class="badge">{{ value }}</span></li>{% endfor %}
           </ul>
           <p class="help">This table shows the number of times this algorithm
-          has been <b>successfuly</b> run using the given environment. Note
+          has been <b>successfully</b> run using the given environment. Note
           this does not provide sufficient information to evaluate if the
           algorithm will run when submitted to different conditions.</p>
           {% endif %}
diff --git a/beat/web/algorithms/views.py b/beat/web/algorithms/views.py
old mode 100644
new mode 100755
index 70839094f..08cb8eced
--- a/beat/web/algorithms/views.py
+++ b/beat/web/algorithms/views.py
@@ -164,19 +164,24 @@ def edit(request, author, name, version):
 
     description = algorithm.description
 
-    parameters = {'algorithm_author':  request.user.username,
-                  'algorithm_name':    name,
-                  'algorithm_version': algorithm.version,
-                  'source_code':       algorithm.source_code_file.read(),
-                  'declaration':       algorithm.declaration_file.read().replace('\n', ''),
-                  'short_description': algorithm.short_description,
-                  'description':       description.replace('\n', '\\n'),
-                  'html_description':  restructuredtext(description).replace('\n', ''),
-                  'messages':          Messages,
-                  'edition':           True,
-                  'plot_account':      settings.PLOT_ACCOUNT,
+    parameters = {'algorithm_author':        request.user.username,
+                  'algorithm_name':          name,
+                  'algorithm_version':       algorithm.version,
+                  'algorithm_language':      algorithm.json_language,
+                  'algorithm_language_name': algorithm.language_fullname(),
+                  'declaration':             algorithm.declaration_file.read().replace('\n', ''),
+                  'short_description':       algorithm.short_description,
+                  'description':             description.replace('\n', '\\n'),
+                  'html_description':        restructuredtext(description).replace('\n', ''),
+                  'messages':                Messages,
+                  'edition':                 True,
+                  'binary':                  algorithm.is_binary(),
+                  'plot_account':            settings.PLOT_ACCOUNT,
                  }
 
+    if not algorithm.is_binary():
+        parameters['source_code'] = algorithm.source_code_file.read()
+
     return render_to_response('algorithms/edition.html',
                               parameters,
                               context_instance=RequestContext(request))
diff --git a/beat/web/code/models.py b/beat/web/code/models.py
index 4ad4eb905..33d98329b 100755
--- a/beat/web/code/models.py
+++ b/beat/web/code/models.py
@@ -209,6 +209,10 @@ class Code(StoredContribution):
         (R,       'R'),
     )
 
+    CODE_NAMES = {
+        CXX: 'C++',
+    }
+
 
     #_____ Fields __________
 
@@ -476,6 +480,16 @@ class Code(StoredContribution):
         return self.language in [Code.CXX]
 
 
+    def language_fullname(self):
+        if Code.CODE_NAMES.has_key(self.language):
+            return Code.CODE_NAMES[self.language]
+        return filter(lambda x: x[0] == self.language, Code.CODE_LANGUAGE)[0][1]
+
+
+    def json_language(self):
+        return filter(lambda x: x[0] == self.language, Code.CODE_LANGUAGE)[0][1].lower()
+
+
     #_____ Overrides __________
 
     def save(self, *args, **kwargs):
diff --git a/beat/web/common/texts.py b/beat/web/common/texts.py
old mode 100644
new mode 100755
index 47a8a154a..543fdaa59
--- a/beat/web/common/texts.py
+++ b/beat/web/common/texts.py
@@ -40,4 +40,5 @@ Messages = {
         'format_name': 'The name for this dataformat (space-like characters will be automatically replaced by dashes)',
         'name': 'The name for this object (space-like characters will be automatically replaced by dashes)',
         'version': 'The version of this object (an integer starting from 1)',
+        'shared_library': 'The compiled shared library file implementing your algorithm',
         }
diff --git a/buildout.cfg b/buildout.cfg
index 096135684..4ecf41387 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -112,6 +112,7 @@ packages = jquery#~1.11.3
            jquery-dateFormat#~1.0.2
            jquery-ui#~1.10.4
            jquery.cookie#~1.4.1
+           jquery-file-upload#~9.14.0
            fontawesome#~4.5.0
            codemirror#~5.10.0
            bootstrap#~3.3.6
-- 
GitLab