Merge branch '2800-python-global-state' into 2800-pgs
authorTom Clegg <tom@curoverse.com>
Sat, 16 Aug 2014 06:06:20 +0000 (02:06 -0400)
committerTom Clegg <tom@curoverse.com>
Sat, 16 Aug 2014 06:06:20 +0000 (02:06 -0400)
Conflicts:
sdk/python/arvados/api.py

268 files changed:
.gitignore
apps/workbench/Gemfile
apps/workbench/Gemfile.lock
apps/workbench/app/assets/javascripts/application.js
apps/workbench/app/assets/javascripts/filterable.js
apps/workbench/app/assets/javascripts/infinite_scroll.js
apps/workbench/app/assets/javascripts/select_modal.js
apps/workbench/app/assets/stylesheets/application.css.scss
apps/workbench/app/assets/stylesheets/select_modal.css.scss
apps/workbench/app/controllers/actions_controller.rb
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/controllers/groups_controller.rb
apps/workbench/app/controllers/projects_controller.rb
apps/workbench/app/controllers/search_controller.rb [new file with mode: 0644]
apps/workbench/app/controllers/user_agreements_controller.rb
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/helpers/application_helper.rb
apps/workbench/app/models/group.rb
apps/workbench/app/models/pipeline_instance.rb
apps/workbench/app/models/user.rb
apps/workbench/app/views/application/404.html.erb
apps/workbench/app/views/application/_choose.html.erb
apps/workbench/app/views/application/_choose.js.erb
apps/workbench/app/views/application/_content.html.erb
apps/workbench/app/views/application/_content_layout.html.erb
apps/workbench/app/views/application/_projects_tree_menu.html.erb
apps/workbench/app/views/application/_tab_line_buttons.html.erb [new file with mode: 0644]
apps/workbench/app/views/application/index.html.erb
apps/workbench/app/views/application/show.html.erb
apps/workbench/app/views/collections/_choose_rows.html.erb
apps/workbench/app/views/collections/_show_files.html.erb
apps/workbench/app/views/collections/_show_recent.html.erb
apps/workbench/app/views/collections/index.html.erb [new file with mode: 0644]
apps/workbench/app/views/collections/show.html.erb
apps/workbench/app/views/groups/_choose_rows.html.erb
apps/workbench/app/views/keep_disks/_content_layout.html.erb
apps/workbench/app/views/layouts/application.html.erb
apps/workbench/app/views/layouts/body.html.erb [new file with mode: 0644]
apps/workbench/app/views/pipeline_instances/_show_inputs.html.erb
apps/workbench/app/views/pipeline_instances/_show_recent.html.erb
apps/workbench/app/views/pipeline_instances/index.html.erb [new file with mode: 0644]
apps/workbench/app/views/pipeline_templates/_choose_rows.html.erb
apps/workbench/app/views/pipeline_templates/_show_components.html.erb
apps/workbench/app/views/pipeline_templates/show.html.erb [new file with mode: 0644]
apps/workbench/app/views/projects/_index_projects.html.erb
apps/workbench/app/views/projects/_show_contents_rows.html.erb
apps/workbench/app/views/projects/_show_data_collections.html.erb
apps/workbench/app/views/projects/_show_jobs_and_pipelines.html.erb
apps/workbench/app/views/projects/_show_other_objects.html.erb
apps/workbench/app/views/projects/_show_pipeline_templates.html.erb
apps/workbench/app/views/projects/_show_sharing.html.erb
apps/workbench/app/views/projects/_show_subprojects.html.erb
apps/workbench/app/views/projects/_show_tab_contents.html.erb
apps/workbench/app/views/projects/index.html.erb
apps/workbench/app/views/projects/show.html.erb [new file with mode: 0644]
apps/workbench/app/views/search/_choose_rows.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_add_ssh_key_popup.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_choose_rows.html.erb
apps/workbench/app/views/users/_manage_account.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_manage_current_token.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_manage_repositories.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_manage_ssh_keys.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_manage_virtual_machines.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_setup_popup.html.erb
apps/workbench/app/views/users/add_ssh_key.js.erb [new file with mode: 0644]
apps/workbench/app/views/users/add_ssh_key_popup.js.erb [new file with mode: 0644]
apps/workbench/app/views/users/manage_account.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/profile.html.erb [new file with mode: 0644]
apps/workbench/config/application.default.yml
apps/workbench/config/application.yml.example
apps/workbench/config/routes.rb
apps/workbench/test/controllers/search_controller_test.rb [new file with mode: 0644]
apps/workbench/test/functional/projects_controller_test.rb
apps/workbench/test/helpers/search_helper_test.rb [new file with mode: 0644]
apps/workbench/test/integration/application_layout_test.rb [new file with mode: 0644]
apps/workbench/test/integration/pipeline_instances_test.rb
apps/workbench/test/integration/pipeline_templates_test.rb [new file with mode: 0644]
apps/workbench/test/integration/projects_test.rb
apps/workbench/test/integration/smoke_test.rb
apps/workbench/test/test_helper.rb
crunch_scripts/arvados-bcbio-nextgen.py
crunch_scripts/collection-merge
crunch_scripts/crunchutil/__init__.py [new file with mode: 0644]
crunch_scripts/crunchutil/robust_put.py [new file with mode: 0644]
crunch_scripts/crunchutil/subst.py [moved from crunch_scripts/subst.py with 91% similarity]
crunch_scripts/crunchutil/vwd.py [new file with mode: 0644]
crunch_scripts/decompress-all.py [new file with mode: 0755]
crunch_scripts/run-command
crunch_scripts/split-fastq.py [new file with mode: 0755]
doc/_config.yml
doc/_includes/_alert_docker.liquid [deleted file]
doc/_includes/_concurrent_hash_script_py.liquid [moved from doc/_includes/_parallel_hash_script_py.liquid with 100% similarity]
doc/_includes/_ssh_addkey.liquid
doc/_includes/_tutorial_bwa_sortsam_pipeline.liquid [new file with mode: 0644]
doc/_includes/_tutorial_expectations.liquid
doc/_layouts/default.html.liquid
doc/api/methods/collections.html.textile.liquid
doc/api/schema/Group.html.textile.liquid
doc/api/schema/PipelineTemplate.html.textile.liquid
doc/css/font-awesome.css [new file with mode: 0644]
doc/css/nav-list.css
doc/fonts/FontAwesome.otf [new file with mode: 0644]
doc/fonts/fontawesome-webfont.eot [new file with mode: 0755]
doc/fonts/fontawesome-webfont.svg [new file with mode: 0755]
doc/fonts/fontawesome-webfont.ttf [new file with mode: 0755]
doc/fonts/fontawesome-webfont.woff [new file with mode: 0755]
doc/images/ssh-adding-public-key.png
doc/install/index.html.textile.liquid
doc/install/install-api-server.html.textile.liquid
doc/install/install-docker.html.textile.liquid
doc/sdk/go/index.html.textile.liquid [new file with mode: 0644]
doc/sdk/index.html.textile.liquid
doc/user/copying/copying.html.textile.liquid
doc/user/examples/crunch-examples.html.textile.liquid
doc/user/getting_started/check-environment.html.textile.liquid
doc/user/getting_started/workbench.html.textile.liquid
doc/user/index.html.textile.liquid
doc/user/reference/api-tokens.html.textile.liquid
doc/user/reference/sdk-cli.html.textile.liquid
doc/user/topics/keep.html.textile.liquid
doc/user/topics/running-pipeline-command-line.html.textile.liquid
doc/user/topics/tutorial-job-debug.html.textile.liquid [deleted file]
doc/user/topics/tutorial-parallel.html.textile.liquid
doc/user/topics/tutorial-trait-search.html.textile.liquid
doc/user/tutorials/intro-crunch.html.textile.liquid
doc/user/tutorials/running-external-program.html.textile.liquid
doc/user/tutorials/tutorial-firstscript.html.textile.liquid
doc/user/tutorials/tutorial-keep-get.html.textile.liquid [new file with mode: 0644]
doc/user/tutorials/tutorial-keep-mount.html.textile.liquid [new file with mode: 0644]
doc/user/tutorials/tutorial-keep.html.textile.liquid
doc/user/tutorials/tutorial-new-pipeline.html.textile.liquid
doc/user/tutorials/tutorial-pipeline-workbench.html.textile.liquid
doc/user/tutorials/tutorial-submit-job.html.textile.liquid [new file with mode: 0644]
docker/api/Dockerfile
docker/api/application.yml.in
docker/api/apt.arvados.org.list [new file with mode: 0644]
docker/api/arvados-clients.yml.in [new file with mode: 0644]
docker/api/crunch-dispatch-run.sh [new file with mode: 0755]
docker/api/keep_server_0.json [new file with mode: 0644]
docker/api/keep_server_1.json [new file with mode: 0644]
docker/api/munge.key [new file with mode: 0644]
docker/api/omniauth.rb.in
docker/api/setup-gitolite.sh.in [new file with mode: 0755]
docker/api/setup.sh.in [new file with mode: 0755]
docker/api/slurm.conf.in [new file with mode: 0644]
docker/api/supervisor.conf
docker/api/update-gitolite.rb [new file with mode: 0755]
docker/arvdock
docker/base/Dockerfile
docker/base/apt.arvados.org.list [new file with mode: 0644]
docker/build.sh
docker/build_tools/Makefile
docker/build_tools/build.rb
docker/build_tools/config.rb
docker/compute/Dockerfile [new file with mode: 0644]
docker/compute/fuse.conf [new file with mode: 0644]
docker/compute/setup.sh.in [new file with mode: 0755]
docker/compute/ssh.sh [new file with mode: 0755]
docker/compute/supervisor.conf [new file with mode: 0644]
docker/compute/wrapdocker [new file with mode: 0755]
docker/config.yml.example
docker/jobs/Dockerfile
docker/keep/Dockerfile
docker/keep/keep_signing_secret.in [new file with mode: 0644]
docker/keep/run-keep.in [new file with mode: 0755]
docker/shell/Dockerfile [new file with mode: 0644]
docker/shell/fuse.conf [new file with mode: 0644]
docker/shell/setup.sh.in [new file with mode: 0755]
docker/shell/superuser_token.in [new file with mode: 0644]
docker/shell/supervisor.conf [new file with mode: 0644]
docker/slurm/Dockerfile [new file with mode: 0644]
docker/slurm/munge.key [new file with mode: 0644]
docker/slurm/slurm.conf.in [new file with mode: 0644]
docker/slurm/supervisor.conf [new file with mode: 0644]
docker/workbench/Dockerfile
docker/workbench/workbench_rails_env.in [new file with mode: 0644]
sdk/cli/bin/arv
sdk/cli/bin/arv-run-pipeline-instance
sdk/cli/bin/crunch-job
sdk/go/arvadosclient/arvadosclient.go [moved from sdk/go/src/arvados.org/sdk/sdk.go with 99% similarity]
sdk/go/arvadosclient/arvadosclient_test.go [moved from sdk/go/src/arvados.org/sdk/sdk_test.go with 95% similarity]
sdk/go/go.sh [deleted file]
sdk/go/keepclient/hashcheck.go [moved from sdk/go/src/arvados.org/keepclient/hashcheck.go with 98% similarity]
sdk/go/keepclient/hashcheck_test.go [moved from sdk/go/src/arvados.org/keepclient/hashcheck_test.go with 100% similarity]
sdk/go/keepclient/keepclient.go [moved from sdk/go/src/arvados.org/keepclient/keepclient.go with 97% similarity]
sdk/go/keepclient/keepclient_test.go [moved from sdk/go/src/arvados.org/keepclient/keepclient_test.go with 92% similarity]
sdk/go/keepclient/support.go [moved from sdk/go/src/arvados.org/keepclient/support.go with 99% similarity]
sdk/go/streamer/streamer.go [moved from sdk/go/src/arvados.org/streamer/streamer.go with 100% similarity]
sdk/go/streamer/streamer_test.go [moved from sdk/go/src/arvados.org/streamer/streamer_test.go with 100% similarity]
sdk/go/streamer/transfer.go [moved from sdk/go/src/arvados.org/streamer/transfer.go with 100% similarity]
sdk/python/.gitignore
sdk/python/arvados/__init__.py
sdk/python/arvados/api.py
sdk/python/arvados/collection.py
sdk/python/arvados/commands/put.py
sdk/python/arvados/errors.py
sdk/python/arvados/events.py
sdk/python/arvados/keep.py
sdk/python/arvados/stream.py
sdk/python/bin/arv-get
sdk/python/bin/arv-ls
sdk/python/bin/arv-normalize
sdk/python/bin/arv-ws
sdk/python/tests/run_test_server.py
sdk/python/tests/test_api.py [new file with mode: 0644]
sdk/python/tests/test_arv_put.py
sdk/python/tests/test_collections.py
sdk/python/tests/test_keep_client.py
sdk/ruby/lib/arvados.rb
services/api/Gemfile
services/api/Gemfile.lock
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/controllers/arvados/v1/groups_controller.rb
services/api/app/controllers/arvados/v1/jobs_controller.rb
services/api/app/controllers/arvados/v1/repositories_controller.rb
services/api/app/controllers/arvados/v1/schema_controller.rb
services/api/app/controllers/arvados/v1/users_controller.rb
services/api/app/mailers/profile_notifier.rb [new file with mode: 0644]
services/api/app/models/arvados_model.rb
services/api/app/models/authorized_key.rb
services/api/app/models/collection.rb
services/api/app/models/job.rb
services/api/app/models/link.rb
services/api/app/models/locator.rb
services/api/app/models/user.rb
services/api/app/views/profile_notifier/profile_created.text.erb [new file with mode: 0644]
services/api/config/application.default.yml
services/api/config/application.yml.example
services/api/config/routes.rb
services/api/db/migrate/20140607150616_rename_folder_to_project.rb [moved from services/api/db/migrate/20140607150616_rename_folder_to_project.rb_deferred with 100% similarity]
services/api/db/migrate/20140714184006_empty_collection.rb
services/api/db/structure.sql
services/api/lib/current_api_client.rb
services/api/script/crunch-dispatch.rb
services/api/test/fixtures/api_client_authorizations.yml
services/api/test/fixtures/authorized_keys.yml
services/api/test/fixtures/collections.yml
services/api/test/fixtures/groups.yml
services/api/test/fixtures/jobs.yml
services/api/test/fixtures/links.yml
services/api/test/fixtures/pipeline_instances.yml
services/api/test/fixtures/users.yml
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/api/test/functional/arvados/v1/groups_controller_test.rb
services/api/test/functional/arvados/v1/job_reuse_controller_test.rb
services/api/test/functional/arvados/v1/repositories_controller_test.rb
services/api/test/functional/arvados/v1/users_controller_test.rb
services/api/test/unit/job_test.rb
services/api/test/unit/user_test.rb
services/crunch/crunchstat/go.sh [deleted file]
services/crunchstat/crunchstat.go [moved from services/crunch/crunchstat/src/arvados.org/crunchstat/crunchstat.go with 100% similarity]
services/fuse/arvados_fuse/__init__.py
services/fuse/bin/arv-mount
services/fuse/tests/test_mount.py
services/keep/go.sh [deleted file]
services/keepproxy/keepproxy.go [moved from services/keep/src/arvados.org/keepproxy/keepproxy.go with 98% similarity]
services/keepproxy/keepproxy_test.go [moved from services/keep/src/arvados.org/keepproxy/keepproxy_test.go with 85% similarity]
services/keepstore/handler_test.go [moved from services/keep/src/keep/handler_test.go with 76% similarity]
services/keepstore/handlers.go [moved from services/keep/src/keep/handlers.go with 83% similarity]
services/keepstore/keepstore.go [moved from services/keep/src/keep/keep.go with 88% similarity]
services/keepstore/keepstore_test.go [moved from services/keep/src/keep/keep_test.go with 97% similarity]
services/keepstore/perms.go [moved from services/keep/src/keep/perms.go with 100% similarity]
services/keepstore/perms_test.go [moved from services/keep/src/keep/perms_test.go with 100% similarity]
services/keepstore/volume.go [moved from services/keep/src/keep/volume.go with 94% similarity]
services/keepstore/volume_unix.go [moved from services/keep/src/keep/volume_unix.go with 87% similarity]
services/keepstore/volume_unix_test.go [moved from services/keep/src/keep/volume_unix_test.go with 100% similarity]

index 0cddee596c4cf665b9359f1a6276c7ecdcfd3d49..8cc6b89324311d0cd7db51f9c6a5e7ba400253dc 100644 (file)
@@ -11,11 +11,9 @@ sdk/perl/Makefile
 sdk/perl/blib
 sdk/perl/pm_to_blib
 */vendor/bundle
-services/keep/bin
-services/keep/pkg
-services/keep/src/github.com
 sdk/java/target
 *.class
 apps/workbench/vendor/bundle
 services/api/vendor/bundle
 sdk/java/log
+sdk/cli/vendor
index 754d5c60437c8d1844902fd1d250ae0af92f2336..a289303e4def4d033a66b563ee51426c3348d5e8 100644 (file)
@@ -49,6 +49,8 @@ gem 'bootstrap-x-editable-rails'
 
 gem 'less'
 gem 'less-rails'
+gem 'wiselinks'
+gem 'sshkey'
 
 # To use ActiveModel has_secure_password
 # gem 'bcrypt-ruby', '~> 3.0.0'
index 173be13cc3e312a64d8a10a5e183cb81f72957bf..3daa501ca5a479d96987410b1d97d00dd2ca1db2 100644 (file)
@@ -167,6 +167,7 @@ GEM
       activesupport (>= 3.0)
       sprockets (~> 2.8)
     sqlite3 (1.3.8)
+    sshkey (1.6.1)
     therubyracer (0.12.0)
       libv8 (~> 3.16.14.0)
       ref
@@ -183,6 +184,7 @@ GEM
       json (>= 1.8.0)
     websocket (1.0.7)
     websocket-driver (0.3.2)
+    wiselinks (1.2.1)
     xpath (2.0.0)
       nokogiri (~> 1.3)
 
@@ -216,6 +218,8 @@ DEPENDENCIES
   simplecov (~> 0.7.1)
   simplecov-rcov
   sqlite3
+  sshkey
   themes_for_rails!
   therubyracer
   uglifier (>= 1.0.3)
+  wiselinks
index d21c4b5625447617b9f4ceaea33f8762e8f69f26..c4cb68322f1454aac3220d8bfc0fba02a7246e86 100644 (file)
@@ -21,6 +21,7 @@
 //= require bootstrap/modal
 //= require bootstrap/button
 //= require bootstrap3-editable/bootstrap-editable
+//= require wiselinks
 //= require_tree .
 
 jQuery(function($){
@@ -135,11 +136,13 @@ jQuery(function($){
         on('ajax:complete ready', function() {
             // See http://getbootstrap.com/javascript/#buttons
             $('.btn').button();
-        });
-
-    $(document).
+        }).
         on('ready ajax:complete', function() {
             $('[data-toggle~=tooltip]').tooltip({container:'body'});
+        }).
+        on('ready ajax:complete', function() {
+            // This makes the dialog close on Esc key, obviously.
+            $('.modal').attr('tabindex', '-1')
         });
 
     HeaderRowFixer = function(selector) {
@@ -177,4 +180,29 @@ jQuery(function($){
         fixer.duplicateTheadTr();
         fixer.fixThead();
     });
+
+    $(document).ready(function() {
+        /* When wiselinks is initialized, selection.js is not working. Since we want to stop
+           using selection.js in the near future, let's not initialize wiselinks for now. */
+
+        // window.wiselinks = new Wiselinks();
+
+        $(document).off('page:loading').on('page:loading', function(event, $target, render, url){
+            $("#page-wrapper").fadeOut(200);
+        });
+
+        $(document).off('page:redirected').on('page:redirected', function(event, $target, render, url){
+        });
+
+        $(document).off('page:always').on('page:always', function(event, xhr, settings){
+            $("#page-wrapper").fadeIn(200);
+        });
+
+        $(document).off('page:done').on('page:done', function(event, $target, status, url, data){
+        });
+
+        $(document).off('page:fail').on('page:fail', function(event, $target, status, url, error, code){
+        });
+    });
+
 });
index 76c5ac3ca1297d23ee8bc867444c231ef177a993..7da461ec44aaa419185943298eb18f6dc088d7d9 100644 (file)
@@ -1,11 +1,12 @@
 $(document).
-    on('paste keyup change', 'input[type=text].filterable-control', function() {
+    on('paste keyup input', 'input[type=text].filterable-control', function() {
         var q = new RegExp($(this).val(), 'i');
         $($(this).attr('data-filterable-target')).
             addClass('filterable-container').
             data('q', q).
             trigger('refresh');
     }).on('refresh', '.filterable-container', function() {
+        var $container = $(this);
         var q = $(this).data('q');
         var filters = $(this).data('filters');
         $('.filterable', this).hide().filter(function() {
@@ -26,6 +27,16 @@ $(document).
             }
             return pass;
         }).show();
+
+        // Show/hide each section heading depending on whether any
+        // content rows are visible in that section.
+        $('.row[data-section-heading]', this).each(function(){
+            $(this).toggle($('.row.filterable[data-section-name="' +
+                             $(this).attr('data-section-name') +
+                             '"]:visible').length > 0);
+        });
+
+        // Load more content if the last result is showing.
         $('.infinite-scroller').add(window).trigger('scroll');
     }).on('change', 'select.filterable-control', function() {
         var val = $(this).val();
@@ -38,5 +49,5 @@ $(document).
             data('filters', filters).
             trigger('refresh');
     }).on('ajax:complete', function() {
-        $('.filterable-control').trigger('change');
+        $('.filterable-control').trigger('input');
     });
index 1f9997efa05fc002f8a9907965d73e9dff2b1a46..e70ca259e4766c0ca00de11b5dcb9d3dc13200ab 100644 (file)
@@ -1,25 +1,31 @@
-function maybe_load_more_content() {
+function maybe_load_more_content(event) {
     var scroller = this;        // element with scroll bars
-    var container;              // element that receives new content
+    var $container;             // element that receives new content
     var src;                    // url for retrieving content
     var scrollHeight;
     var spinner, colspan;
+    var serial = Date.now();
     scrollHeight = scroller.scrollHeight || $('body')[0].scrollHeight;
-    var num_scrollers = $(window).data("arv-num-scrollers");
     if ($(scroller).scrollTop() + $(scroller).height()
         >
         scrollHeight - 50)
     {
-      for (var i = 0; i < num_scrollers; i++) {
-        $container = $($(this).data('infinite-container'+i));
+        $container = $(event.data.container);
+        if (!$container.attr('data-infinite-content-href0')) {
+            // Remember the first page source url, so we can refresh
+            // from page 1 later.
+            $container.attr('data-infinite-content-href0',
+                            $container.attr('data-infinite-content-href'));
+        }
         src = $container.attr('data-infinite-content-href');
         if (!src || !$container.is(':visible'))
-          continue;
+            // Finished
+            return;
 
         // Don't start another request until this one finishes
         $container.attr('data-infinite-content-href', null);
         spinner = '<div class="spinner spinner-32px spinner-h-center"></div>';
-        if ($(container).is('table,tbody,thead,tfoot')) {
+        if ($container.is('table,tbody,thead,tfoot')) {
             // Hack to determine how many columns a new tr should have
             // in order to reach full width.
             colspan = $container.closest('table').
@@ -30,16 +36,21 @@ function maybe_load_more_content() {
                        spinner +
                        '</td></tr>');
         }
+        $container.find(".spinner").detach();
         $container.append(spinner);
+        $container.attr('data-infinite-serial', serial);
         $.ajax(src,
                {dataType: 'json',
                 type: 'GET',
-                data: {},
-                context: {container: $container, src: src}}).
-            always(function() {
-                $(this.container).find(".spinner").detach();
-            }).
+                data: ($container.data('infinite-content-params') || {}),
+                context: {container: $container, src: src, serial: serial}}).
             fail(function(jqxhr, status, error) {
+                var $faildiv;
+                var $container = this.container;
+                if ($container.attr('data-infinite-serial') != this.serial) {
+                    // A newer request is already in progress.
+                    return;
+                }
                 if (jqxhr.readyState == 0 || jqxhr.status == 0) {
                     message = "Cancelled."
                 } else if (jqxhr.responseJSON && jqxhr.responseJSON.errors) {
@@ -47,32 +58,60 @@ function maybe_load_more_content() {
                 } else {
                     message = "Request failed.";
                 }
-                // TODO: report this to the user.
+                // TODO: report the message to the user.
                 console.log(message);
-                $(this.container).attr('data-infinite-content-href', this.src);
+                $faildiv = $('<div />').
+                    attr('data-infinite-content-href', this.src).
+                    addClass('infinite-retry').
+                    append('<span class="fa fa-warning" /> Oops, request failed. <button class="btn btn-xs btn-primary">Retry</button>');
+                $container.find('div.spinner').replaceWith($faildiv);
             }).
             done(function(data, status, jqxhr) {
-                $(this.container).append(data.content);
-                $(this.container).attr('data-infinite-content-href', data.next_page_href);
+                if ($container.attr('data-infinite-serial') != this.serial) {
+                    // A newer request is already in progress.
+                    return;
+                }
+                $container.find(".spinner").detach();
+                $container.append(data.content);
+                $container.attr('data-infinite-content-href', data.next_page_href);
             });
-        break;
      }
-   }
 }
 
 $(document).
+    on('click', 'div.infinite-retry button', function() {
+        var $retry_div = $(this).closest('.infinite-retry');
+        var $scroller = $(this).closest('.infinite-scroller')
+        $scroller.attr('data-infinite-content-href',
+                       $retry_div.attr('data-infinite-content-href'));
+        $retry_div.replaceWith('<div class="spinner spinner-32px spinner-h-center" />');
+        $scroller.trigger('scroll');
+    }).
+    on('refresh-content', '[data-infinite-scroller]', function() {
+        // Clear all rows, reset source href to initial state, and
+        // (if the container is visible) start loading content.
+        var first_page_href = $(this).attr('data-infinite-content-href0');
+        if (!first_page_href)
+            first_page_href = $(this).attr('data-infinite-content-href');
+        $(this).
+            html('').
+            attr('data-infinite-content-href', first_page_href);
+        $('.infinite-scroller').
+            trigger('scroll');
+    }).
     on('ready ajax:complete', function() {
-        var num_scrollers = 0;
         $('[data-infinite-scroller]').each(function() {
+            if ($(this).hasClass('infinite-scroller-ready'))
+                return;
+            $(this).addClass('infinite-scroller-ready');
+
             var $scroller = $($(this).attr('data-infinite-scroller'));
             if (!$scroller.hasClass('smart-scroll') &&
                 'scroll' != $scroller.css('overflow-y'))
                 $scroller = $(window);
             $scroller.
                 addClass('infinite-scroller').
-                data('infinite-container'+num_scrollers, this).
-                on('scroll', maybe_load_more_content);
-            num_scrollers++;
+                on('scroll resize', { container: this }, maybe_load_more_content).
+                trigger('scroll');
         });
-        $(window).data("arv-num-scrollers", num_scrollers);
     });
index cd23556a4accc83007172bd2d3e24cfea261d1cb..1fec3dcb95e394405cebe8bc799d5491ef7e2d4e 100644 (file)
@@ -1,14 +1,14 @@
 $(document).on('click', '.selectable', function() {
     var any;
     var $this = $(this);
-    if (!$this.hasClass('multiple')) {
-        $this.closest('.selectable-container').
+    var $container = $(this).closest('.selectable-container');
+    if (!$container.hasClass('multiple')) {
+        $container.
             find('.selectable').
             removeClass('active');
     }
     $this.toggleClass('active');
-    any = ($this.
-           closest('.selectable-container').
+    any = ($container.
            find('.selectable.active').length > 0)
     $this.
         closest('.modal').
@@ -16,14 +16,19 @@ $(document).on('click', '.selectable', function() {
         prop('disabled', !any);
 
     if ($this.hasClass('active')) {
+        var no_preview_available = '<div class="spinner-h-center spinner-v-center"><center>(No preview available)</center></div>';
+        if (!$this.attr('data-preview-href')) {
+            $(".modal-dialog-preview-pane").html(no_preview_available);
+            return;
+        }
         $(".modal-dialog-preview-pane").html('<div class="spinner spinner-32px spinner-h-center spinner-v-center"></div>');
         $.ajax($this.attr('data-preview-href'),
                {dataType: "html"}).
-           done(function(data, status, jqxhr) {
+            done(function(data, status, jqxhr) {
                 $(".modal-dialog-preview-pane").html(data);
             }).
             fail(function(data, status, jqxhr) {
-                $(".modal-dialog-preview-pane").text('Preview load failed.');
+                $(".modal-dialog-preview-pane").html(no_preview_available);
             });
     }
 
@@ -65,12 +70,72 @@ $(document).on('click', '.selectable', function() {
             $(document).trigger(event_name!=null ? event_name : 'page-refresh',
                                 [data, status, jqxhr, this.action_data]);
         });
-});
-$(document).on('page-refresh', function(event, data, status, jqxhr, action_data) {
+}).on('click', '.chooser-show-project', function() {
+    var params = {};
+    var project_uuid = $(this).attr('data-project-uuid');
+    $(this).attr('href', '#');  // Skip normal click handler
+    if (project_uuid) {
+        params = {'filters[]': JSON.stringify(['owner_uuid',
+                                               '=',
+                                               project_uuid]),
+                  project_uuid: project_uuid
+                 };
+    }
+    // Use current selection as dropdown button label
+    $(this).
+        closest('.dropdown-menu').
+        prev('button').
+        html($(this).text() + ' <span class="caret"></span>');
+    // Set (or unset) filter params and refresh filterable rows
+    $($(this).closest('[data-filterable-target]').attr('data-filterable-target')).
+        data('infinite-content-params', params).
+        trigger('refresh-content');
+}).on('ready', function() {
+    $('form[data-search-modal] a').on('click', function() {
+        $(this).closest('form').submit();
+        return false;
+    });
+    $('form[data-search-modal]').on('submit', function() {
+        // Ask the server for a Search modal. When it arrives, copy
+        // the search string from the top nav input into the modal's
+        // search query field.
+        var $form = $(this);
+        var searchq = $form.find('input').val();
+        var is_a_uuid = /^([0-9a-f]{32}(\+\S+)?|[0-9a-z]{5}-[0-9a-z]{5}-[0-9a-z]{15})$/;
+        if (searchq.trim().match(is_a_uuid)) {
+            window.location = '/actions?uuid=' + encodeURIComponent(searchq.trim());
+            // Show the "loading" indicator. TODO: better page transition hook
+            $(document).trigger('ajax:send');
+            return false;
+        }
+        if ($form.find('a[data-remote]').length > 0) {
+            // A search dialog is already loading.
+            return false;
+        }
+        $('<a />').
+            attr('href', $form.attr('data-search-modal')).
+            attr('data-remote', 'true').
+            attr('data-method', 'GET').
+            hide().
+            appendTo($form).
+            on('ajax:success', function(data, status, xhr) {
+                $('body > .modal-container input[type=text]').
+                    val($form.find('input').val()).
+                    focus();
+                $form.find('input').val('');
+            }).on('ajax:complete', function() {
+                $(this).detach();
+            }).
+            click();
+        return false;
+    });
+}).on('page-refresh', function(event, data, status, jqxhr, action_data) {
     window.location.reload();
 }).on('tab-refresh', function(event, data, status, jqxhr, action_data) {
     $(document).trigger('arv:pane:reload:all');
     $('body > .modal-container .modal').modal('hide');
 }).on('redirect-to-created-object', function(event, data, status, jqxhr, action_data) {
     window.location.href = data.href.replace(/^[^\/]*\/\/[^\/]*/, '');
+}).on('shown.bs.modal', 'body > .modal-container .modal', function() {
+    $('.focus-on-display', this).focus();
 });
index 75d58ed5171184bf7d855267ed99ac4d353f7758..6b91783e60abe882bbe7b5ba06fb33a4979e790f 100644 (file)
@@ -146,6 +146,10 @@ nav.navbar-fixed-top .navbar-nav.navbar-right > li > a:hover {
     margin-bottom: -15px;
 }
 
+.infinite-scroller .fa-warning {
+    color: #800;
+}
+
 .inline-progress-container div.progress {
     margin-bottom: 0;
 }
@@ -240,3 +244,7 @@ div.pane-content iframe {
   width: 100%;
   border: none;
 }
+
+div.rounded {
+  border-radius: 3px;
+}
index 09017697b646298a2a71d34b5c16c60db662a5bb..4295f30ecef75308bd2feea0e452205b5358cc12 100644 (file)
@@ -14,3 +14,6 @@
     background: #428bca;
     color: #fff;
 }
+.selectable-container > .row.class-separator {
+    background: #ddd;
+}
index 9a76e9aed412522ffa14166e0b9aadbe1f8e8acf..d1dc0fca354b7969d77317ec161a58d5f245ea62 100644 (file)
@@ -10,6 +10,19 @@ class ActionsController < ApplicationController
     ArvadosBase::resource_class_for_uuid(params[:uuid])
   end
 
+  def show
+    @object = model_class.andand.find(params[:uuid])
+    if @object.is_a? Link and
+        @object.link_class == 'name' and
+        ArvadosBase::resource_class_for_uuid(@object.head_uuid) == Collection
+      redirect_to collection_path(id: @object.uuid)
+    elsif @object
+      redirect_to @object
+    else
+      raise ActiveRecord::RecordNotFound
+    end
+  end
+
   def post
     params.keys.collect(&:to_sym).each do |param|
       if @@exposed_actions[param]
index f739ee1046bb569f6e55bd4aef72cbd786d7fc26..222888085d9e3bb95973f2a2865c3efbb569b7f5 100644 (file)
@@ -12,8 +12,11 @@ class ApplicationController < ActionController::Base
   # Methods that don't require login should
   #   skip_around_filter :require_thread_api_token
   around_filter :require_thread_api_token, except: ERROR_ACTIONS
+  before_filter :accept_uuid_as_id_param, except: ERROR_ACTIONS
   before_filter :check_user_agreements, except: ERROR_ACTIONS
+  before_filter :check_user_profile, except: [:update_profile] + ERROR_ACTIONS
   before_filter :check_user_notifications, except: ERROR_ACTIONS
+  before_filter :load_filters_and_paging_params, except: ERROR_ACTIONS
   before_filter :find_object_by_uuid, except: [:index, :choose] + ERROR_ACTIONS
   theme :select_theme
 
@@ -86,7 +89,10 @@ class ApplicationController < ActionController::Base
     end
   end
 
-  def find_objects_for_index
+  def load_filters_and_paging_params
+    @order = params[:order] || 'created_at desc'
+    @order = [@order] unless @order.is_a? Array
+
     @limit ||= 200
     if params[:limit]
       @limit = params[:limit].to_i
@@ -102,10 +108,22 @@ class ApplicationController < ActionController::Base
       filters = params[:filters]
       if filters.is_a? String
         filters = Oj.load filters
+      elsif filters.is_a? Array
+        filters = filters.collect do |filter|
+          if filter.is_a? String
+            # Accept filters[]=["foo","=","bar"]
+            Oj.load filter
+          else
+            # Accept filters=[["foo","=","bar"]]
+            filter
+          end
+        end
       end
       @filters += filters
     end
+  end
 
+  def find_objects_for_index
     @objects ||= model_class
     @objects = @objects.filter(@filters).limit(@limit).offset(@offset)
   end
@@ -114,10 +132,8 @@ class ApplicationController < ActionController::Base
     respond_to do |f|
       f.json { render json: @objects }
       f.html {
-        if params['tab_pane']
-          comparable = self.respond_to? :compare
-          render(partial: 'show_' + params['tab_pane'].downcase,
-                 locals: { comparable: comparable, objects: @objects })
+        if params[:tab_pane]
+          render_pane params[:tab_pane]
         else
           render
         end
@@ -126,6 +142,23 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  helper_method :render_pane
+  def render_pane tab_pane, opts={}
+    render_opts = {
+      partial: 'show_' + tab_pane.downcase,
+      locals: {
+        comparable: self.respond_to?(:compare),
+        objects: @objects,
+        tab_pane: tab_pane
+      }.merge(opts[:locals] || {})
+    }
+    if opts[:to_string]
+      render_to_string render_opts
+    else
+      render render_opts
+    end
+  end
+
   def index
     find_objects_for_index if !@objects
     render_index
@@ -148,6 +181,13 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  helper_method :next_page_href
+  def next_page_href with_params={}
+    if next_page_offset
+      url_for with_params.merge(offset: next_page_offset)
+    end
+  end
+
   def show
     if !@object
       return render_not_found("object not found")
@@ -156,9 +196,7 @@ class ApplicationController < ActionController::Base
       f.json { render json: @object.attributes.merge(href: url_for(@object)) }
       f.html {
         if params['tab_pane']
-          comparable = self.respond_to? :compare
-          render(partial: 'show_' + params['tab_pane'].downcase,
-                 locals: { comparable: comparable, objects: @objects })
+          render_pane params['tab_pane']
         elsif request.method.in? ['GET', 'HEAD']
           render
         else
@@ -171,26 +209,14 @@ class ApplicationController < ActionController::Base
 
   def choose
     params[:limit] ||= 40
-    if !@objects
-      if params[:project_uuid] and !params[:project_uuid].empty?
-        # We want the chooser to show objects of the controllers's model_class
-        # type within a specific project specified by project_uuid, so fetch the
-        # project and request the contents of the project filtered on the
-        # controllers's model_class kind.
-        @objects = Group.find(params[:project_uuid]).contents({:filters => [['uuid', 'is_a', "arvados\##{ArvadosApiClient.class_kind(model_class)}"]]})
-      end
-      find_objects_for_index if !@objects
-    end
+    find_objects_for_index if !@objects
     respond_to do |f|
       if params[:partial]
         f.json {
           render json: {
             content: render_to_string(partial: "choose_rows.html",
-                                      formats: [:html],
-                                      locals: {
-                                        multiple: params[:multiple]
-                                      }),
-            next_page_href: @next_page_href
+                                      formats: [:html]),
+            next_page_href: next_page_href(partial: params[:partial])
           }
         }
       end
@@ -341,10 +367,14 @@ class ApplicationController < ActionController::Base
     end
   end
 
-  def find_object_by_uuid
+
+  def accept_uuid_as_id_param
     if params[:id] and params[:id].match /\D/
       params[:uuid] = params.delete :id
     end
+  end
+
+  def find_object_by_uuid
     begin
       if not model_class
         @object = nil
@@ -380,9 +410,6 @@ class ApplicationController < ActionController::Base
     Thread.current[:arvados_api_token] = new_token
     if new_token.nil?
       Thread.current[:user] = nil
-    elsif (new_token == session[:arvados_api_token]) and
-        session[:user].andand[:is_active]
-      Thread.current[:user] = User.new(session[:user])
     else
       Thread.current[:user] = User.current
     end
@@ -400,15 +427,7 @@ class ApplicationController < ActionController::Base
       false  # We may redirect to login, or not, based on the current action.
     else
       session[:arvados_api_token] = params[:api_token]
-      session[:user] = {
-        uuid: user.uuid,
-        email: user.email,
-        first_name: user.first_name,
-        last_name: user.last_name,
-        is_active: user.is_active,
-        is_admin: user.is_admin,
-        prefs: user.prefs
-      }
+
       if !request.format.json? and request.method.in? ['GET', 'HEAD']
         # Repeat this request with api_token in the (new) session
         # cookie instead of the query string.  This prevents API
@@ -502,6 +521,41 @@ class ApplicationController < ActionController::Base
     true
   end
 
+  def check_user_profile
+    if request.method.downcase != 'get' || params[:partial] ||
+       params[:tab_pane] || params[:action_method] ||
+       params[:action] == 'setup_popup'
+      return true
+    end
+
+    if missing_required_profile?
+      render 'users/profile'
+    end
+    true
+  end
+
+  helper_method :missing_required_profile?
+  def missing_required_profile?
+    missing_required = false
+
+    profile_config = Rails.configuration.user_profile_form_fields
+    if current_user && profile_config
+      current_user_profile = current_user.prefs[:profile]
+      profile_config.kind_of?(Array) && profile_config.andand.each do |entry|
+        if entry['required']
+          if !current_user_profile ||
+             !current_user_profile[entry['key'].to_sym] ||
+             current_user_profile[entry['key'].to_sym].empty?
+            missing_required = true
+            break
+          end
+        end
+      end
+    end
+
+    missing_required
+  end
+
   def select_theme
     return Rails.configuration.arvados_theme
   end
@@ -569,7 +623,7 @@ class ApplicationController < ActionController::Base
   helper_method :all_projects
   def all_projects
     @all_projects ||= Group.
-      filter([['group_class','in',['project','folder']]]).order('name')
+      filter([['group_class','=','project']]).order('name')
   end
 
   helper_method :my_projects
@@ -609,8 +663,8 @@ class ApplicationController < ActionController::Base
     (Job.limit(10) |
      PipelineInstance.limit(10)).
       sort_by do |x|
-      x.finished_at || x.started_at || x.created_at rescue x.created_at
-    end
+      (x.finished_at || x.started_at rescue nil) || x.modified_at || x.created_at
+    end.reverse
   end
 
   helper_method :my_project_tree
@@ -680,7 +734,7 @@ class ApplicationController < ActionController::Base
     crumbs = []
     current = @name_link || @object
     while current
-      if current.is_a?(Group) and current.group_class.in?(['project','folder'])
+      if current.is_a?(Group) and current.group_class == 'project'
         crumbs.prepend current
       end
       if current.is_a? Link
@@ -694,7 +748,7 @@ class ApplicationController < ActionController::Base
 
   helper_method :current_project_uuid
   def current_project_uuid
-    if @object.is_a? Group and @object.group_class.in?(['project','folder'])
+    if @object.is_a? Group and @object.group_class == 'project'
       @object.uuid
     elsif @name_link.andand.tail_uuid
       @name_link.tail_uuid
@@ -872,4 +926,7 @@ class ApplicationController < ActionController::Base
     @objects_for
   end
 
+  def wiselinks_layout
+    'body'
+  end
 end
index 5a7a52207bcebdc946eb3289c618d97b2cddd78c..bea72a4274b9d8748bffbbde9a3e7937ef616879 100644 (file)
@@ -45,22 +45,17 @@ class CollectionsController < ApplicationController
   def choose
     params[:limit] ||= 40
 
-    filter = [['link_class','=','name'],
-              ['head_uuid','is_a','arvados#collection']]
-
-    if params[:project_uuid] and !params[:project_uuid].empty?
-      filter << ['tail_uuid', '=', params[:project_uuid]]
-    end
-
-    @objects = Link.filter(filter)
+    @filters += [['link_class','=','name'],
+                 ['head_uuid','is_a','arvados#collection']]
 
+    @objects = Link
     find_objects_for_index
-    @next_page_href = (next_page_offset and
-                       url_for(offset: next_page_offset, partial: true))
+
     @name_links = @objects
 
     @objects = Collection.
       filter([['uuid','in',@name_links.collect(&:head_uuid)]])
+    preload_links_for_objects @objects.to_a
     super
   end
 
index 7698fdba934cb68e641ea23d7ffe92635aa09ffd..080386ea5c6bf6185bcd57e744014203e4060be8 100644 (file)
@@ -1,6 +1,6 @@
 class GroupsController < ApplicationController
   def index
-    @groups = Group.filter [['group_class', 'not in', ['folder', 'project']]]
+    @groups = Group.filter [['group_class', '!=', 'project']]
     @group_uuids = @groups.collect &:uuid
     @links_from = Link.where link_class: 'permission', tail_uuid: @group_uuids
     @links_to = Link.where link_class: 'permission', head_uuid: @group_uuids
@@ -8,7 +8,7 @@ class GroupsController < ApplicationController
   end
 
   def show
-    if @object.group_class.in?(['project','folder'])
+    if @object.group_class == 'project'
       redirect_to(project_path(@object))
     else
       super
index f32f356d0ca88257adaa4ff5969cadcaef326659..6287793f765bf82ddd0e15e3037f7a11c7d3c05a 100644 (file)
@@ -3,6 +3,31 @@ class ProjectsController < ApplicationController
     Group
   end
 
+  def find_object_by_uuid
+    if current_user and params[:uuid] == current_user.uuid
+      @object = current_user.dup
+      @object.uuid = current_user.uuid
+      class << @object
+        def name
+          'Home'
+        end
+        def description
+          ''
+        end
+        def attribute_editable? attr, *args
+          case attr
+          when 'description', 'name'
+            false
+          else
+            super
+          end
+        end
+      end
+    else
+      super
+    end
+  end
+
   def index_pane_list
     %w(Projects)
   end
@@ -52,6 +77,32 @@ class ProjectsController < ApplicationController
     end
   end
 
+  def move_items
+    target_uuid = params['target']
+    uuids_to_add = session[:selected_move_items]
+
+    uuids_to_add.
+      collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
+      uniq.
+      each do |resource_class|
+      resource_class.filter([['uuid','in',uuids_to_add]]).each do |dst|
+        if resource_class == Collection
+          dst = Link.new(owner_uuid: target_uuid,
+                         tail_uuid: target_uuid,
+                         head_uuid: dst.uuid,
+                         link_class: 'name',
+                         name: target_uuid)
+        else
+          dst.owner_uuid = target_uuid
+          dst.tail_uuid = target_uuid if dst.class == Link
+        end
+        dst.save!
+      end
+    end
+    session[:selected_move_items] = nil
+    redirect_to @object
+  end
+
   def destroy
     while (objects = Link.filter([['owner_uuid','=',@object.uuid],
                                   ['tail_uuid','=',@object.uuid]])).any?
@@ -77,48 +128,86 @@ class ProjectsController < ApplicationController
     super
   end
 
+  def load_contents_objects kinds=[]
+    kind_filters = @filters.select do |attr,op,val|
+      op == 'is_a' and val.is_a? Array and val.count > 1
+    end
+    if /^created_at\b/ =~ @order[0] and kind_filters.count == 1
+      # If filtering on multiple types and sorting by date: Get the
+      # first page of each type, sort the entire set, truncate to one
+      # page, and use the last item on this page as a filter for
+      # retrieving the next page. Ideally the API would do this for
+      # us, but it doesn't (yet).
+      nextpage_operator = /\bdesc$/i =~ @order[0] ? '<' : '>'
+      @objects = []
+      @name_link_for = {}
+      kind_filters.each do |attr,op,val|
+        (val.is_a?(Array) ? val : [val]).each do |type|
+          objects = @object.contents(order: @order,
+                                     limit: @limit,
+                                     include_linked: true,
+                                     filters: (@filters - kind_filters + [['uuid', 'is_a', type]]),
+                                     offset: @offset)
+          objects.each do |object|
+            @name_link_for[object.andand.uuid] = objects.links_for(object, 'name').first
+          end
+          @objects += objects
+        end
+      end
+      @objects = @objects.to_a.sort_by(&:created_at)
+      @objects.reverse! if nextpage_operator == '<'
+      @objects = @objects[0..@limit-1]
+      @next_page_filters = @filters.reject do |attr,op,val|
+        attr == 'created_at' and op == nextpage_operator
+      end
+      if @objects.any?
+        @next_page_filters += [['created_at',
+                                nextpage_operator,
+                                @objects.last.created_at]]
+        @next_page_href = url_for(partial: :contents_rows,
+                                  filters: @next_page_filters.to_json)
+      else
+        @next_page_href = nil
+      end
+    else
+      @objects = @object.contents(order: @order,
+                                  limit: @limit,
+                                  include_linked: true,
+                                  filters: @filters,
+                                  offset: @offset)
+      @next_page_href = next_page_href(partial: :contents_rows)
+    end
+  end
+
   def show
     if !@object
       return render_not_found("object not found")
     end
-    @objects = @object.contents(limit: 50,
-                                include_linked: true,
-                                filters: params[:filters],
-                                offset: params[:offset] || 0)
-    @logs = Log.limit(10).filter([['object_uuid', '=', @object.uuid]])
-    @users = User.limit(10000).
-      select(["uuid", "is_active", "first_name", "last_name"]).
-      filter([['is_active', '=', 'true']])
-    @groups = Group.limit(10000).
-      select(["uuid", "name", "description"])
 
-    begin
-      @share_links = Link.permissions_for(@object)
-      @user_is_manager = true
-    rescue ArvadosApiClient::AccessForbiddenException,
-           ArvadosApiClient::NotFoundException
-      @share_links = []
-      @user_is_manager = false
+    @user_is_manager = false
+    @share_links = []
+    if @object.uuid != current_user.uuid
+      begin
+        @share_links = Link.permissions_for(@object)
+        @user_is_manager = true
+      rescue ArvadosApiClient::AccessForbiddenException,
+        ArvadosApiClient::NotFoundException
+      end
     end
 
-    @objects_and_names = get_objects_and_names @objects
-
     if params[:partial]
+      load_contents_objects
       respond_to do |f|
         f.json {
           render json: {
             content: render_to_string(partial: 'show_contents_rows.html',
-                                      formats: [:html],
-                                      locals: {
-                                        objects_and_names: @objects_and_names,
-                                        project: @object
-                                      }),
-            next_page_href: (next_page_offset and
-                             url_for(offset: next_page_offset, filters: params[:filters], partial: true))
+                                      formats: [:html]),
+            next_page_href: @next_page_href
           }
         }
       end
     else
+      @objects = []
       super
     end
   end
@@ -135,13 +224,17 @@ class ProjectsController < ApplicationController
   end
 
   helper_method :get_objects_and_names
-  def get_objects_and_names(objects)
+  def get_objects_and_names(objects=nil)
+    objects = @objects if objects.nil?
     objects_and_names = []
     objects.each do |object|
-      if !(name_links = objects.links_for(object, 'name')).empty?
+      if objects.respond_to? :links_for and
+          !(name_links = objects.links_for(object, 'name')).empty?
         name_links.each do |name_link|
           objects_and_names << [object, name_link]
         end
+      elsif @name_link_for.andand[object.uuid]
+        objects_and_names << [object, @name_link_for[object.uuid]]
       elsif object.respond_to? :name
         objects_and_names << [object, object]
       else
diff --git a/apps/workbench/app/controllers/search_controller.rb b/apps/workbench/app/controllers/search_controller.rb
new file mode 100644 (file)
index 0000000..6f209a5
--- /dev/null
@@ -0,0 +1,26 @@
+class SearchController < ApplicationController
+  def find_objects_for_index
+    search_what = Group
+    if params[:project_uuid]
+      # Special case for "search all things in project":
+      @filters = @filters.select do |attr, operator, operand|
+        not (attr == 'owner_uuid' and operator == '=')
+      end
+      # Special case for project_uuid is a user uuid:
+      if ArvadosBase::resource_class_for_uuid(params[:project_uuid]) == User
+        search_what = User.find params[:project_uuid]
+      else
+        search_what = Group.find params[:project_uuid]
+      end
+    end
+    @objects = search_what.contents(limit: @limit,
+                                    offset: @offset,
+                                    filters: @filters,
+                                    include_linked: true)
+    super
+  end
+
+  def next_page_href with_params={}
+    super with_params.merge(last_object_class: @objects.last.class.to_s)
+  end
+end
index 6ab8ae215f488888b04bcb2362f52596e26603f7..924bf44baeee801735d905a431743bddd49e0ff8 100644 (file)
@@ -1,6 +1,7 @@
 class UserAgreementsController < ApplicationController
   skip_before_filter :check_user_agreements
   skip_before_filter :find_object_by_uuid
+  skip_before_filter :check_user_profile
 
   def model_class
     Collection
index 0313de5aa2e7b9559146b9cfca7a6f31b166862d..67b51a9bc9d043513c82fb6d8b2145a4f0462e68 100644 (file)
@@ -210,6 +210,73 @@ class UsersController < ApplicationController
     end
   end
 
+  def manage_account
+    # repositories current user can read / write
+    repo_links = []
+    Link.filter([['head_uuid', 'is_a', 'arvados#repository'],
+                 ['tail_uuid', '=', current_user.uuid],
+                 ['link_class', '=', 'permission'],
+                 ['name', 'in', ['can_write', 'can_read']],
+               ]).
+          each do |perm_link|
+            repo_links << perm_link[:head_uuid]
+          end
+    @my_repositories = Repository.where(uuid: repo_links)
+
+    # virtual machines the current user can login into
+    @my_vm_logins = {}
+    Link.where(tail_uuid: current_user.uuid,
+               link_class: 'permission',
+               name: 'can_login').
+          each do |perm_link|
+            if perm_link.properties.andand[:username]
+              @my_vm_logins[perm_link.head_uuid] ||= []
+              @my_vm_logins[perm_link.head_uuid] << perm_link.properties[:username]
+            end
+          end
+    @my_virtual_machines = VirtualMachine.where(uuid: @my_vm_logins.keys)
+
+    # current user's ssh keys
+    @my_ssh_keys = AuthorizedKey.where(key_type: 'SSH', owner_uuid: current_user.uuid)
+
+    respond_to do |f|
+      f.html { render template: 'users/manage_account' }
+    end
+  end
+
+  def add_ssh_key_popup
+    respond_to do |format|
+      format.html
+      format.js
+    end
+  end
+
+  def add_ssh_key
+    respond_to do |format|
+      key_params = {'key_type' => 'SSH'}
+      key_params['authorized_user_uuid'] = current_user.uuid
+
+      if params['name'] && params['name'].size>0
+        key_params['name'] = params['name'].strip
+      end
+      if params['public_key'] && params['public_key'].size>0
+        key_params['public_key'] = params['public_key'].strip
+      end
+
+      if !key_params['name'] && params['public_key'].andand.size>0
+        split_key = key_params['public_key'].split
+        key_params['name'] = split_key[-1] if (split_key.size == 3)
+      end
+
+      new_key = AuthorizedKey.create! key_params
+      if new_key
+        format.js
+      else
+        self.render_error status: 422
+      end
+    end
+  end
+
   protected
 
   def find_current_links user
index c3856c2f299bfb0747d7cc81d5394338e9a67547..428c14f8282961f65e6ad69e9197794f62997d54 100644 (file)
@@ -260,12 +260,18 @@ module ApplicationHelper
           display_value = value_info[:link_name]
         end
       end
+      if (attr == :components) and (subattr.size > 2)
+        chooser_title = "Choose a dataset for #{object.component_input_title(subattr[0], subattr[2])}:"
+      else
+        chooser_title = "Choose a dataset:"
+      end
       modal_path = choose_collections_path \
-      ({ title: 'Choose a dataset:',
-         filters: [['tail_uuid', '=', object.owner_uuid]].to_json,
+      ({ title: chooser_title,
+         filters: [['owner_uuid', '=', object.owner_uuid]].to_json,
          action_name: 'OK',
          action_href: pipeline_instance_path(id: object.uuid),
          action_method: 'patch',
+         preconfigured_search_str: "#{value_info[:search_for]}",
          action_data: {
            merge: true,
            selection_param: selection_param,
@@ -428,4 +434,13 @@ module ApplicationHelper
       RESOURCE_CLASS_ICONS.fetch(class_name, default)
     end
   end
+
+  def chooser_preview_url_for object
+    case object.class.to_s
+    when 'Collection'
+      polymorphic_path(object, tab_pane: 'chooser_preview')
+    else
+      nil
+    end
+  end
 end
index 9e627bf66ea012871ee8a536799d79ae40de3e9a..558c587a1c9c83d3a0bb824475e0b8f2553e4a0d 100644 (file)
@@ -3,6 +3,15 @@ class Group < ArvadosBase
     true
   end
 
+  def self.contents params={}
+    res = arvados_api_client.api self, "/contents", {
+      _method: 'GET'
+    }.merge(params)
+    ret = ArvadosResourceList.new
+    ret.results = arvados_api_client.unpack_api_response(res)
+    ret
+  end
+
   def contents params={}
     res = arvados_api_client.api self.class, "/#{self.uuid}/contents", {
       _method: 'GET'
@@ -13,7 +22,7 @@ class Group < ArvadosBase
   end
 
   def class_for_display
-    group_class.in?(['folder', 'project']) ? 'Project' : super
+    group_class == 'project' ? 'Project' : super
   end
 
   def editable?
index c3b14755f3fb6f8dbdf77d3630e689797cca8eea..fa9fab68eb6e149ecef2e062ff332aa9a6f42fd4 100644 (file)
@@ -28,7 +28,7 @@ class PipelineInstance < ArvadosBase
       end
     end
   end
-  
+
   def attribute_editable? attr, *args
     super && (attr.to_sym == :name ||
               (attr.to_sym == :components and
@@ -42,4 +42,11 @@ class PipelineInstance < ArvadosBase
   def self.creatable?
     false
   end
+
+  def component_input_title(component_name, input_name)
+    component = components[component_name]
+    return nil if component.nil?
+    component[:script_parameters].andand[input_name.to_sym].andand[:title] ||
+      "\"#{input_name.to_s}\" parameter for #{component[:script]} script in #{component_name} component"
+  end
 end
index 9c914776a2cf43b5ef0241dfced63c0a745a1bb5..87ea5faefa85976b8d11350b291dffc28b2514d7 100644 (file)
@@ -27,11 +27,15 @@ class User < ArvadosBase
                                                {}))
   end
 
+  def contents params={}
+    Group.contents params.merge(uuid: self.uuid)
+  end
+
   def attributes_for_display
     super.reject { |k,v| %w(owner_uuid default_owner_uuid identity_url prefs).index k }
   end
 
- def attribute_editable? attr, *args
 def attribute_editable? attr, *args
     (not (self.uuid.andand.match(/000000000000000$/) and self.is_admin)) and super
   end
 
@@ -49,4 +53,10 @@ class User < ArvadosBase
     arvados_api_client.api(self, "/setup", params)
   end
 
+  def update_profile params
+    self.private_reload(arvados_api_client.api(self.class,
+                                               "/#{self.uuid}/profile",
+                                               params))
+  end
+
 end
index 40d73b9a737141e90381e326d44c1f5c20e615b3..fd972959560faf14ae74a061d348cddc5d1d7554 100644 (file)
@@ -1,7 +1,8 @@
 <%
    if (controller.andand.action_name == 'show') and params[:uuid]
-     class_name = controller.model_class.to_s.underscore.humanize(capitalize: false)
-     req_item = safe_join([class_name, " with UUID ",
+     class_name = controller.model_class.to_s.underscore
+     class_name_h = class_name.humanize(capitalize: false)
+     req_item = safe_join([class_name_h, " with UUID ",
                            raw("<code>"), params[:uuid], raw("</code>")], "")
    else
      req_item = "page you requested"
@@ -14,7 +15,7 @@
 
 <% if class_name %>
 Perhaps you'd like to
-<%= link_to("browse all #{class_name.pluralize}", action: :index) %>?
+<%= link_to("browse all #{class_name_h.pluralize}", action: :index, controller: class_name.tableize) %>?
 <% end %>
 
 </p>
index 3c1aaadf7d5746d34b612a208195a9ec583da726..68351a94995121f5fbf90860412fbf4f5c2a4ee9 100644 (file)
@@ -7,48 +7,59 @@
       </div>
 
       <div class="modal-body">
-        <nav class="navbar navbar-default breadcrumbs" role="navigation">
-          <ul class="nav navbar-nav navbar-left">
-            <li class="dropdown">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="projects-menu">
-              Projects
-              <span class="caret"></span>
-            </a>
-            <ul class="dropdown-menu" role="menu">
-            <%= render partial: "projects_tree_menu", locals: {
-                  :project_link_to => Proc.new do |pnode, &block|
-                    link_to "#", {"class" => "chooser-show-project", "data-project-uuid" => pnode[:object].uuid }, &block
-                   end,
-                  :top_button => Proc.new do %>
-                    <% link_to "#", {"class" => "chooser-show-project btn btn-xs btn-default pull-right" } do %>
-                      All <%= controller.model_class.class_for_display.pluralize.downcase %>
-                    <% end %>
+        <div class="input-group">
+          <% if params[:by_project].to_s != "false" %>
+            <% selected_project_name = 'All projects'
+               @filters.andand.each do |attr, op, val|
+                 if attr == 'owner_uuid' and op == '='
+                   if val == current_user.uuid
+                     selected_project_name = "Home"
+                   else
+                     selected_project_name = Group.find(val).name rescue val
+                   end
+                 end
+               end
+               %>
+            <div class="input-group-btn" data-filterable-target=".modal.arv-choose .selectable-container">
+              <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+                <%= selected_project_name %> <span class="caret"></span>
+              </button>
+              <ul class="dropdown-menu" role="menu">
+                <li>
+                  <%= link_to '#', class: 'chooser-show-project' do %>
+                    All projects
                   <% end %>
-            <% } %>
-            </ul>
-            </li>
-            <li class="nav-separator">
-              <i class="fa fa-lg fa-angle-double-right"></i>
-            </li>
-            <li><p class="navbar-text" id="chooser-breadcrumb">All <%= controller.model_class.class_for_display.pluralize.downcase %></span></li>
-          </ul>
-          <div class="navbar-form navbar-right">
-            <input type="text" class="form-control filterable-control" placeholder="Search" data-filterable-target=".modal.arv-choose .selectable-container"/>
-          </div>
-        </nav>
+                </li>
+                <li class="divider" />
+                <%= render partial: "projects_tree_menu", locals: {
+                      :project_link_to => Proc.new do |pnode, &block|
+                        link_to "#", {
+                          class: "chooser-show-project",
+                          data: {'project_uuid' => pnode[:object].uuid},
+                        }, &block
+                      end,
+                      :top_button => nil
+                    } %>
+              </ul>
+            </div>
+          <% end %>
+          <input type="text" value="<%=params[:preconfigured_search_str] || ''%>" class="form-control filterable-control focus-on-display" placeholder="Search" data-filterable-target=".modal.arv-choose .selectable-container"/>
+        </div>
+        <div style="height: 1em" />
 
-        <% preview_pane = (params[:preview_pane] != "false")
-           pane_col_class = preview_pane ? "col-sm-6" : "" %>
+        <% preview_pane = (params[:preview_pane].to_s != "false")
+           pane_col_class = preview_pane ? "col-sm-6" : "col-sm-12" %>
         <div class="row" style="height: 20em">
-          <div class="<%= pane_col_class %> container-fluid arv-filterable-list selectable-container"
-               style="height: 100%; overflow-y: scroll"
-               data-infinite-scroller="#choose-scroll"
-               id="choose-scroll"
-               data-infinite-content-href="<%= @next_page_href %>">
-            <%= render partial: 'choose_rows', locals: {multiple: multiple} %>
-          </div>
+         <div class="<%= pane_col_class %> container arv-filterable-list selectable-container <%= 'multiple' if multiple %>"
+              style="height: 100%; overflow-y: scroll"
+              data-infinite-scroller="#choose-scroll"
+              id="choose-scroll"
+              data-infinite-content-href="<%= next_page_href partial: true %>"
+              data-infinite-content-href0="<%= url_for partial: true %>">
+           <%= render partial: 'choose_rows' %>
+         </div>
           <% if preview_pane %>
-          <div class="<%= pane_col_class %> modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
+          <div class="col-sm-6 visible-md visible-lg modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
           </div>
           <% end %>
         </div>
index b033c9bf2fedf170c765b178bb510494707888ca..c482b4597efb70e5b531b88d9ac75caa8de0851f 100644 (file)
@@ -1,3 +1,4 @@
+<% session[:selected_move_items] = params['move_items'] if params['move_items'] %>
 $('body > .modal-container').html("<%= escape_javascript(render partial: 'choose.html', locals: {multiple: multiple}) %>");
 $('body > .modal-container .modal').modal('show');
 $('body > .modal-container .modal .modal-footer .btn-primary').
@@ -5,24 +6,3 @@ $('body > .modal-container .modal .modal-footer .btn-primary').
     attr('data-action-href', '<%= j params[:action_href] %>').
     attr('data-method', '<%= j params[:action_method] %>').
     data('action-data', <%= raw params[:action_data] %>);
-$(".chooser-show-project").on("click", function() {
-  $("#choose-scroll").html("<div class=\"spinner spinner-32px spinner-h-center\"></div>");
-  $(".modal-dialog-preview-pane").html('');
-  var t = $(this);
-  var d = {
-      partial: true,
-      multiple: <%= multiple || "false" %>
-    };
-  if (t.attr("data-project-uuid") != null) {
-    d.project_uuid = t.attr("data-project-uuid");
-  }
-  $.ajax('<%=j url_for %>', {
-    dataType: "json",
-    type: "GET",
-    data: d
-  }).done(function(data, status, jqxhr) {
-    $("#chooser-breadcrumb").text(t.text());
-    $("#choose-scroll").html(data.content);
-    $("#choose-scroll").prop("data-infinite-content-href", "next_page_href");
-  });
-});
index 1609a66b09065506a5d6ff4e9f3589262a06126c..f7ae90912f51f9facc73b8566d75063eaf3814ee 100644 (file)
@@ -1,24 +1,3 @@
-<% content_for :content_top do %>
-  <% if @object and not @object.is_a?(Group) and @object.class.goes_in_projects? and @object.owner_uuid == current_user.uuid and not @name_link %>
-    <div class="pull-right" style="width: 40%">
-      <div class="alert alert-warning alert-dismissable">
-        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
-        <strong>Hey.</strong> This <%= @object.class_for_display.downcase %> belongs to your account, but it's not in any of your projects. If you want it to be easy to find in the future, you should move it to a project.<br />
-        <%= button_to(choose_projects_path(
-                   title: 'Move to...',
-                   editable: true,
-                   action_name: 'Move',
-                   action_href: url_for(action: :update),
-                   action_method: 'patch',
-                   action_data: {selection_param: @object.resource_param_name+'[owner_uuid]', success: 'page-refresh'}.to_json),
-                  { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
-          <i class="fa fa-fw fa-folder"></i> Choose a project...
-        <% end %>
-      </div>
-    </div>
-  <% end %>
-<% end %>
-
 <% content_for :tab_panes do %>
 
 <% comparable = controller.respond_to? :compare %>
@@ -42,8 +21,7 @@
     <div id="<%= pane %>-scroll" style="margin-top:0.5em;">
       <div class="pane-content">
         <% if i == 0 %>
-          <%= render(partial: 'show_' + pane.downcase,
-                     locals: { comparable: comparable, objects: @objects }) %>
+          <%= render_pane pane, to_string: true %>
         <% else %>
           <div class="spinner spinner-32px spinner-h-center"></div>
         <% end %>
index c8e38277201b7a465fcc53a1f7b82556d7c18f31..3e50b6eb8c3a97e19fb485909a2dbaf2fcd819a9 100644 (file)
@@ -1,13 +1,6 @@
 <%= content_for :content_top %>
-  <% if @object and @object.is_a?(Group) and @object.group_class.in?(['project','folder']) %>
-  <div class="pull-right">
-    <%= content_for :tab_line_buttons %>
-  </div>
-  <br clear="all" />
-<% else %>
-  <br clear="all" />
-  <div class="pull-right">
-    <%= content_for :tab_line_buttons %>
-  </div>
-<% end %>
+<div class="pull-right">
+  <%= content_for :tab_line_buttons %>
+</div>
+<br clear="all" />
 <%= content_for :tab_panes %>
index 876b0be65c8120e9d64a16b5d2cf03f73017c944..600c6ab96fcfca4f8a5980746e9a7fca8ffa75e9 100644 (file)
@@ -1,12 +1,13 @@
-              <li role="presentation" class="dropdown-header">
-                <%= top_button.call %>
-                My projects
+              <li>
+                <%= project_link_to.call({object: current_user, depth: 0}) do %>
+                  <span style="padding-left: 0"></span>Home
+                <% end %>
               </li>
               <% my_project_tree.each do |pnode| %>
                 <% next if pnode[:object].class != Group %>
-                <li style="padding-left: <%= pnode[:depth]-1 %>em">
+                <li>
                   <%= project_link_to.call pnode do %>
-                    <%= pnode[:object].name %>
+                    <span style="padding-left: <%= pnode[:depth] %>em"></span><%= pnode[:object].name %>
                   <% end %>
                 </li>
               <% end %>
@@ -16,9 +17,9 @@
               </li>
               <% shared_project_tree.each do |pnode| %>
                 <% next if pnode[:object].class != Group %>
-                <li style="padding-left: <%= pnode[:depth]-1 %>em">
+                <li>
                   <%= project_link_to.call pnode do %>
-                    <i class="fa fa-fw fa-share-alt" style="color:#aaa"></i> <%= pnode[:object].name %>
+                    <span style="padding-left: <%= pnode[:depth]-1 %>em"></span><i class="fa fa-fw fa-share-alt" style="color:#aaa"></i> <%= pnode[:object].name %>
                   <% end %>
                 </li>
               <% end %>
diff --git a/apps/workbench/app/views/application/_tab_line_buttons.html.erb b/apps/workbench/app/views/application/_tab_line_buttons.html.erb
new file mode 100644 (file)
index 0000000..e69de29
index 0e72f7a2ddadf4f7947cb08b2afc0aa90a5cde50..3e2a608ed719cef61c52f17a07865bfdd6ca9dbd 100644 (file)
@@ -13,6 +13,8 @@
         <i class="fa fa-fw fa-plus"></i> Add a new user
       <% end %>
       <div id="user-setup-modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"></div>
+    <% elsif controller.controller_name == 'manage_account' %>
+      <%# No add button is needed %>
     <% else %>
       <%= button_to({action: 'create'}, {class: 'btn btn-sm btn-primary'}) do %>
         <i class="fa fa-fw fa-plus"></i>
index d6eca3a2155d2e7c50b58e030884fc5c8f323dcc..105e1c356d69f140cc7bcc6cbf0a4069a9be2e61 100644 (file)
@@ -27,4 +27,3 @@
 <% end %>
 
 <%= render partial: 'content', layout: 'content_layout', locals: {pane_list: controller.show_pane_list }%>
-
index d87f56f9cd4136c40009c71e67113271b6fde2b5..8079eb44a6593563eb67705947acd784b4e0e594 100644 (file)
@@ -1,13 +1,15 @@
 <% @name_links.each do |name_link| %>
   <% if (object = get_object(name_link.head_uuid)) %>
-    <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= name_link.uuid %>"
-         data-preview-href="<%= url_for object %>?tab_pane=chooser_preview"
+    <div class="row filterable selectable" data-object-uuid="<%= name_link.uuid %>"
+         data-preview-href="<%= chooser_preview_url_for object %>"
          style="margin-left: 1em; border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: #DDDDDD">
-        <i class="fa fa-fw fa-archive"></i>
-        <%= name_link.name %>
-    <% Link.filter([['link_class','=','tag'],['head_uuid','=',object.uuid]]).collect(&:name).each do |tagname| %>
-      <span class="label label-info"><%= tagname %></span>
-    <% end %>
+      <i class="fa fa-fw fa-archive"></i>
+      <%= name_link.name %>
+      <% links_for_object(object).each do |tag| %>
+        <% if tag.link_class == 'tag' %>
+          <span class="label label-info"><%= tag.name %></span>
+        <% end %>
+      <% end %>
     </div>
   <% end %>
 <% end %>
index 8576d4044069266e971023eb635f333482de14b8..ea55577e7077a28d464975dc49b60a8d50b54900 100644 (file)
@@ -1,8 +1,3 @@
-<% content_for :tab_line_buttons do %>
-<span style="padding-left: 1em">Collection storage status:</span>
-<%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
-<% end %>
-
 <% file_tree = @object.andand.files_tree %>
 <% if file_tree.nil? or file_tree.empty? %>
   <p>This collection is empty.</p>
index 2f81073305ad4f7598d61a133ddf8802a0f621e1..d624749a4a1e4a9645ee678e61b3594e0db3f7e5 100644 (file)
@@ -1,16 +1,3 @@
-<% content_for :tab_line_buttons do %>
- <%= form_tag collections_path, method: 'get', remote: true, class: 'form-search' do %>
- <div class="input-group">
-   <%= text_field_tag :search, params[:search], class: 'form-control', placeholder: 'Search collections' %>
-   <span class="input-group-btn">
-     <%= button_tag(class: 'btn btn-info') do %>
-     <span class="glyphicon glyphicon-search"></span>
-     <% end %>
-   </span>
- </div>
- <% end %>
-<% end %>
-
 <%= render partial: "paging", locals: {results: @collections, object: @object} %>
 
 <div style="padding-right: 1em">
diff --git a/apps/workbench/app/views/collections/index.html.erb b/apps/workbench/app/views/collections/index.html.erb
new file mode 100644 (file)
index 0000000..061b05b
--- /dev/null
@@ -0,0 +1,14 @@
+<% content_for :tab_line_buttons do %>
+ <%= form_tag collections_path, method: 'get', remote: true, class: 'form-search' do %>
+ <div class="input-group">
+   <%= text_field_tag :search, params[:search], class: 'form-control', placeholder: 'Search collections' %>
+   <span class="input-group-btn">
+     <%= button_tag(class: 'btn btn-info') do %>
+     <span class="glyphicon glyphicon-search"></span>
+     <% end %>
+   </span>
+ </div>
+ <% end %>
+<% end %>
+
+<%= render file: 'application/index.html.erb', locals: local_assigns %>
index a51c450ea0824e99f30e51322c12cff1710e4427..83dcb4511bfbc36aaae6910492c48e777bcd9cf0 100644 (file)
@@ -90,4 +90,9 @@
   </div>
 </div>
 
-<%= render file: 'application/show.html.erb' %>
+<% content_for :tab_line_buttons do %>
+  <span style="padding-left: 1em">Collection storage status:</span>
+  <%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
+<% end %>
+
+<%= render file: 'application/show.html.erb', locals: local_assigns %>
index 772ef197d03410a561106edb09e852190d6840c4..fca0415e292e3ce06f462f371c8ba2a7734d9866 100644 (file)
@@ -1,6 +1,6 @@
 <% icon_class = fa_icon_class_for_class(Group) %>
 <% @objects.each do |object| %>
-  <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+  <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>">
     <div class="col-sm-12" style="overflow-x:hidden">
       <i class="fa fa-fw <%= icon_class %>"></i>
       <%= object.name %>
index 0f5cd7aeceaa209f3be7a64ceb981cf13167498d..56177ae6e57cef33c7c9b959a8287eb0f96a5008 100644 (file)
@@ -17,5 +17,7 @@
   <% end %>
 <% end %>
 <%= content_for :content_top %>
-<%= content_for :tab_line_buttons %>
+<div class="pull-right">
+  <%= content_for :tab_line_buttons %>
+</div>
 <%= content_for :tab_panes %>
index d0b27c375823cfdf3d7142c1124075a279f3e523..0a309db5b559bc6da69b5a30c75e76432ed131ca 100644 (file)
     }
   </style>
   <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css" rel="stylesheet">
+<%= piwik_tracking_tag %>
 </head>
 <body>
-  <div id="wrapper" class="container-fluid">
-    <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
-      <div class="navbar-header">
-        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
-          <span class="sr-only">Toggle navigation</span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </button>
-        <a class="navbar-brand" href="/"><%= Rails.configuration.site_name.downcase rescue Rails.application.class.parent_name %></a>
-      </div>
-
-      <div class="collapse navbar-collapse">
-        <ul class="nav navbar-nav navbar-right">
-
-          <li>
-            <a><i class="rotating loading glyphicon glyphicon-refresh"></i></a>
-          </li>
-
-          <% if current_user %>
-          <!-- XXX placeholder for this when search is implemented
-          <li>
-            <form class="navbar-form" role="search">
-              <div class="input-group" style="width: 220px">
-                <input type="text" class="form-control" placeholder="search">
-                <span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>
-              </div>
-            </form>
-          </li>
-          -->
-
-          <li class="dropdown notification-menu">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
-              <span class="badge badge-alert notification-count"><%= @notification_count %></span>
-              <%= current_user.email %>
-            </a>
-            <ul class="dropdown-menu" role="menu">
-              <% if current_user.is_active %>
-              <li role="presentation"><a href="/authorized_keys" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage ssh keys</a></li>
-              <li role="presentation"><a href="/api_client_authorizations" role="menuitem"><i class="fa fa-ticket fa-fw"></i> Manage API tokens</a></li>
-              <li role="presentation" class="divider"></li>
-              <% end %>
-              <li role="presentation"><a href="<%= logout_path %>" role="menuitem"><i class="fa fa-sign-out fa-fw"></i> Log out</a></li>
-              <% if current_user.is_active and
-                    (@notifications || []).length > 0 %>
-                <li role="presentation" class="divider"></li>
-                <% @notifications.each_with_index do |n, i| %>
-                  <% if i > 0 %><li class="divider"></li><% end %>
-                  <li class="notification"><%= n.call(self) %></li>
-                <% end %>
-              <% end %>
-            </ul>
-          </li>
-
-          <li class="dropdown selection-menu">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
-              <span class="fa fa-lg fa-paperclip"></span>
-              <span class="badge" id="persistent-selection-count"></span>
-            </a>
-            <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
-              <%= form_tag '/actions' do %>
-                <%= hidden_field_tag 'uuid', @object.andand.uuid %>
-                <div id="selection-form-content"></div>
-              <% end %>
-            </ul>
-          </li>
-
-          <% if current_user.is_active %>
-            <li class="dropdown">
-              <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="system-menu">
-                <span class="fa fa-lg fa-gear"></span>
-              </a>
-              <ul class="dropdown-menu" role="menu">
-                <li role="presentation" class="dropdown-header">
-                  Settings
-                </li>
-                <li role="presentation"><a href="/repositories">
-                    <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
-                </a></li>
-                <li role="presentation"><a href="/virtual_machines">
-                    <i class="fa fa-lg fa-terminal fa-fw"></i> Virtual machines
-                </a></li>
-                <li role="presentation"><a href="/links">
-                    <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
-                </a></li>
-                <% if current_user.andand.is_admin %>
-                  <li role="presentation"><a href="/users">
-                      <i class="fa fa-lg fa-user fa-fw"></i> Users
-                  </a></li>
-                <% end %>
-                <li role="presentation"><a href="/groups">
-                    <i class="fa fa-lg fa-users fa-fw"></i> Groups
-                </a></li>
-                <li role="presentation"><a href="/nodes">
-                    <i class="fa fa-lg fa-cloud fa-fw"></i> Compute nodes
-                </a></li>
-                <li role="presentation"><a href="/keep_services">
-                    <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
-                </a></li>
-                <li role="presentation"><a href="/keep_disks">
-                    <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
-                </a></li>
-              </ul>
-            </li>
-          <% end %>
-          <% else %>
-            <li><a href="<%= arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
-          <% end %>
-
-          <li class="dropdown help-menu">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="arv-help">
-              <span class="fa fa-lg fa-question-circle"></span>
-            </a>
-            <ul class="dropdown-menu">
-              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
-              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
-              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
-            </ul>
-          </li>
-        </ul>
-      </div><!-- /.navbar-collapse -->
-    </nav>
-
-    <% if current_user.andand.is_active %>
-      <nav class="navbar navbar-default breadcrumbs" role="navigation">
-        <ul class="nav navbar-nav navbar-left">
-          <li>
-            <a href="/">
-              <i class="fa fa-lg fa-fw fa-home"></i>
-              Home
-            </a>
-          </li>
-          <li class="nav-separator">
-            <i class="fa fa-lg fa-angle-double-right"></i>
-          </li>
-          <li class="dropdown">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="projects-menu">
-              Projects
-              <span class="caret"></span>
-            </a>
-            <ul class="dropdown-menu" role="menu">
-            <%= render partial: "projects_tree_menu", locals: {
-                  :project_link_to => Proc.new do |pnode, &block|
-                    link_to(project_path(pnode[:object].uuid), data: {object_uuid: pnode[:object].uuid, name: 'name'}, &block)
-                  end,
-                  :top_button => Proc.new do %>
-                    <% link_to projects_path, method: 'post', class: 'btn btn-xs btn-default pull-right' do %>
-                      <i class="fa fa-plus"></i> New project
-                    <% end %>
-                  <% end %>
-            <% } %>
-            </ul>
-          </li>
-          <% project_breadcrumbs.each do |p| %>
-            <li class="nav-separator">
-              <i class="fa fa-lg fa-angle-double-right"></i>
-            </li>
-            <li>
-              <%= link_to(p.name, project_path(p.uuid), data: {object_uuid: p.uuid, name: 'name'}) %>
-            </li>
-          <% end %>
-        </ul>
-      </nav>
-    <% end %>
-
-    <div id="page-wrapper">
-      <%= yield %>
-    </div>
-  </div>
-
-  <%= yield :footer_html %>
-  <%= piwik_tracking_tag %>
-  <%= javascript_tag do %>
-  <%= yield :footer_js %>
-  <% end %>
-
-<div class="modal-container"></div>
+<%= render template: 'layouts/body' %>
+<%= javascript_tag do %>
+<%= yield :footer_js %>
+<% end %>
 </body>
 </html>
diff --git a/apps/workbench/app/views/layouts/body.html.erb b/apps/workbench/app/views/layouts/body.html.erb
new file mode 100644 (file)
index 0000000..4c58dab
--- /dev/null
@@ -0,0 +1,204 @@
+  <div id="wrapper" class="container-fluid">
+    <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
+      <div class="navbar-header">
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+          <span class="sr-only">Toggle navigation</span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+        </button>
+        <a class="navbar-brand" href="/" data-push=true><%= Rails.configuration.site_name.downcase rescue Rails.application.class.parent_name %></a>
+      </div>
+
+      <div class="collapse navbar-collapse">
+        <ul class="nav navbar-nav navbar-right">
+
+          <li>
+            <a><i class="rotating loading glyphicon glyphicon-refresh"></i></a>
+          </li>
+
+          <% if current_user %>
+          <li>
+            <form class="navbar-form" role="search"
+                       data-search-modal=
+                       "<%= url_for(
+                        action: 'choose',
+                        controller: 'search',
+                        title: 'Search',
+                        action_name: 'Show',
+                        action_href: url_for(controller: :actions, action: :show),
+                        action_method: 'get',
+                        action_data: {selection_param: 'uuid', success: 'redirect-to-created-object', copy_from_search_box: true}.to_json)
+                       %>">
+              <div class="input-group" style="width: 220px">
+                <input type="text" class="form-control" placeholder="search">
+                <a class="input-group-addon"><span class="glyphicon glyphicon-search"></span></a>
+              </div>
+            </form>
+          </li>
+
+          <li class="dropdown notification-menu">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
+              <span class="badge badge-alert notification-count"><%= @notification_count %></span>
+              <%= current_user.email %>
+            </a>
+            <ul class="dropdown-menu" role="menu">
+              <li role="presentation" class="dropdown-header">
+                My account
+              </li>
+              <% if current_user.is_active %>
+              <li role="presentation"><a href="/manage_account" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage account</a></li>
+              <% if Rails.configuration.user_profile_form_fields %>
+                <li role="presentation"><a href="/users/<%=current_user.uuid%>/profile" role="menuitem"><i class="fa fa-key fa-fw"></i> Manage profile</a></li>
+              <% end %>
+              <li role="presentation" class="divider"></li>
+              <% end %>
+              <li role="presentation"><a href="<%= logout_path %>" role="menuitem"><i class="fa fa-sign-out fa-fw"></i> Log out</a></li>
+              <% if current_user.is_active and
+                    (@notifications || []).length > 0 %>
+                <li role="presentation" class="divider"></li>
+                <% @notifications.each_with_index do |n, i| %>
+                  <% if i > 0 %><li class="divider"></li><% end %>
+                  <li class="notification"><%= n.call(self) %></li>
+                <% end %>
+              <% end %>
+            </ul>
+          </li>
+
+          <li class="dropdown selection-menu">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+              <span class="fa fa-lg fa-paperclip"></span>
+              <span class="badge" id="persistent-selection-count"></span>
+            </a>
+            <ul class="dropdown-menu" role="menu" id="persistent-selection-list">
+              <%= form_tag '/actions' do %>
+                <%= hidden_field_tag 'uuid', @object.andand.uuid %>
+                <div id="selection-form-content"></div>
+              <% end %>
+            </ul>
+          </li>
+
+          <% if current_user.is_active %>
+            <li class="dropdown">
+              <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="system-menu">
+                <span class="fa fa-lg fa-gear"></span>
+              </a>
+              <ul class="dropdown-menu" role="menu">
+                <li role="presentation" class="dropdown-header">
+                  Settings
+                </li>
+                <% if current_user.andand.is_admin %>
+                  <li role="presentation"><a href="/repositories">
+                      <i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
+                  </a></li>
+                  <li role="presentation"><a href="/virtual_machines">
+                      <i class="fa fa-lg fa-terminal fa-fw"></i> Virtual machines
+                  </a></li>
+                  <li role="presentation"><a href="/authorized_keys">
+                      <i class="fa fa-lg fa-key fa-fw"></i> SSH keys
+                  </a></li>
+                  <li role="presentation"><a href="/api_client_authorizations">
+                      <i class="fa fa-lg fa-ticket fa-fw"></i> API tokens
+                  </a></li>
+                <% end %>
+                <li role="presentation"><a href="/links">
+                    <i class="fa fa-lg fa-arrows-h fa-fw"></i> Links
+                </a></li>
+                <% if current_user.andand.is_admin %>
+                  <li role="presentation"><a href="/users">
+                      <i class="fa fa-lg fa-user fa-fw"></i> Users
+                  </a></li>
+                <% end %>
+                <li role="presentation"><a href="/groups">
+                    <i class="fa fa-lg fa-users fa-fw"></i> Groups
+                </a></li>
+                <li role="presentation"><a href="/nodes">
+                    <i class="fa fa-lg fa-cloud fa-fw"></i> Compute nodes
+                </a></li>
+                <li role="presentation"><a href="/keep_services">
+                    <i class="fa fa-lg fa-exchange fa-fw"></i> Keep services
+                </a></li>
+                <li role="presentation"><a href="/keep_disks">
+                    <i class="fa fa-lg fa-hdd-o fa-fw"></i> Keep disks
+                </a></li>
+              </ul>
+            </li>
+          <% end %>
+          <% else %>
+            <li><a href="<%= arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
+          <% end %>
+
+          <li class="dropdown help-menu">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="arv-help">
+              <span class="fa fa-lg fa-question-circle"></span>
+            </a>
+            <ul class="dropdown-menu">
+              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
+              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
+              <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
+            </ul>
+          </li>
+        </ul>
+      </div><!-- /.navbar-collapse -->
+    </nav>
+
+    <% if current_user.andand.is_active %>
+      <nav class="navbar navbar-default breadcrumbs" role="navigation">
+        <ul class="nav navbar-nav navbar-left">
+          <li>
+            <a href="/">
+              <i class="fa fa-lg fa-fw fa-dashboard"></i>
+              Dashboard
+            </a>
+          </li>
+          <li class="dropdown">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="projects-menu">
+              Projects
+              <span class="caret"></span>
+            </a>
+            <ul class="dropdown-menu" role="menu">
+            <%= render partial: "projects_tree_menu", locals: {
+                  :project_link_to => Proc.new do |pnode, &block|
+                    link_to(project_path(pnode[:object].uuid),
+                      data: { 'object-uuid' => pnode[:object].uuid,
+                              'name' => 'name' },
+                      &block)
+                  end,
+                  :top_button => Proc.new do %>
+                    <% link_to projects_path, method: 'post', class: 'btn btn-xs btn-default pull-right' do %>
+                      <i class="fa fa-plus"></i> New project
+                    <% end %>
+                  <% end %>
+            <% } %>
+            </ul>
+          </li>
+          <% if @name_link or @object %>
+            <li class="nav-separator">
+              <i class="fa fa-lg fa-angle-double-right"></i>
+            </li>
+            <li>
+              <%= link_to project_path(current_user.uuid) do %>
+                Home
+              <% end %>
+            </li>
+            <% project_breadcrumbs.each do |p| %>
+              <li class="nav-separator">
+                <i class="fa fa-lg fa-angle-double-right"></i>
+              </li>
+              <li>
+                <%= link_to(p.name, project_path(p.uuid), data: {object_uuid: p.uuid, name: 'name'}) %>
+              </li>
+            <% end %>
+          <% end %>
+        </ul>
+      </nav>
+    <% end %>
+
+    <div id="page-wrapper">
+      <%= yield %>
+    </div>
+  </div>
+
+  <%= yield :footer_html %>
+
+<div class="modal-container"></div>
index 5106710b0eb1c580ca5f393f265b806f64c914c6..45739192861efe22d26a287ed6f7610c3c1d9e36 100644 (file)
@@ -12,8 +12,7 @@
                   not pvalue_spec[:value])) %>
             <% n_inputs += 1 %>
             <label for="<% "#{cname}-#{pname}" %>">
-              <%= pvalue_spec[:title] ||
-                  "\"#{pname.to_s}\" parameter for #{component[:script]} script in #{cname} component" %>
+              <%= @object.component_input_title(cname, pname) %>
             </label>
             <div>
               <p class="form-control-static">
index 0b78e07d65e875facfee37ee65fba34ad76847e1..08b24f13cb03faa1dcb17c1c1830f90aa5706c64 100644 (file)
@@ -1,10 +1,3 @@
-<%= content_for :tab_line_buttons do %>
-<%= form_tag({action: 'compare', controller: params[:controller], method: 'get'}, {method: 'get', id: 'compare', class: 'pull-right small-form-margin'}) do |f| %>
-  <%= submit_tag 'Compare 2 or 3 selected', {class: 'btn btn-primary', disabled: true, style: 'display: none'} %>
-  &nbsp;
-<% end rescue nil %>
-<% end %>
-
 <%= render partial: "paging", locals: {results: @objects, object: @object} %>
 
 <%= form_tag do |f| %>
diff --git a/apps/workbench/app/views/pipeline_instances/index.html.erb b/apps/workbench/app/views/pipeline_instances/index.html.erb
new file mode 100644 (file)
index 0000000..4b73bd4
--- /dev/null
@@ -0,0 +1,8 @@
+<% content_for :tab_line_buttons do %>
+<%= form_tag({action: 'compare', controller: params[:controller], method: 'get'}, {method: 'get', id: 'compare', class: 'pull-right small-form-margin'}) do |f| %>
+  <%= submit_tag 'Compare 2 or 3 selected', {class: 'btn btn-primary', disabled: true, style: 'display: none'} %>
+  &nbsp;
+<% end rescue nil %>
+<% end %>
+
+<%= render file: 'application/index.html.erb', locals: local_assigns %>
index 9aebd695bf681cb25214bbeab566a45444d9d5f1..9b96b47d19a5193ee218d01ced11702716a13b54 100644 (file)
@@ -1,5 +1,5 @@
 <% @objects.each do |object| %>
-  <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= url_for object %>?tab_pane=chooser_preview">
+  <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= url_for object %>?tab_pane=chooser_preview">
     <div class="col-sm-12" style="overflow-x:hidden">
       <i class="fa fa-fw fa-gear"></i>
       <%= object.name %>
index 1f2c1baaf4930dda3da6a179652d9fe43fdf7fe8..cd03a5c790ef6bbde11590dc18d4acc4f621ef41 100644 (file)
@@ -1,18 +1 @@
-<% content_for :tab_line_buttons do %>
-  <%= button_to(choose_projects_path(id: "run-pipeline-button",
-                                     title: 'Choose project',
-                                     editable: true,
-                                     action_name: 'Choose',
-                                     action_href: pipeline_instances_path,
-                                     action_method: 'post',
-                                     action_data: {selection_param: 'pipeline_instance[owner_uuid]',
-                                                   'pipeline_instance[pipeline_template_uuid]' => @object.uuid,
-                                                   'success' => 'redirect-to-created-object'
-                                                  }.to_json),
-                { class: "btn btn-primary btn-sm", remote: true, method: 'get' }
-               ) do %>
-                   Run this pipeline
-                 <% end %>
-<% end %>
-
 <%= render_pipeline_components("editable", :json, editable: false) %>
diff --git a/apps/workbench/app/views/pipeline_templates/show.html.erb b/apps/workbench/app/views/pipeline_templates/show.html.erb
new file mode 100644 (file)
index 0000000..725d63d
--- /dev/null
@@ -0,0 +1,18 @@
+<% content_for :tab_line_buttons do %>
+  <%= button_to(choose_projects_path(id: "run-pipeline-button",
+                                     title: 'Choose project',
+                                     editable: true,
+                                     action_name: 'Choose',
+                                     action_href: pipeline_instances_path,
+                                     action_method: 'post',
+                                     action_data: {selection_param: 'pipeline_instance[owner_uuid]',
+                                                   'pipeline_instance[pipeline_template_uuid]' => @object.uuid,
+                                                   'success' => 'redirect-to-created-object'
+                                                  }.to_json),
+                { class: "btn btn-primary btn-sm", remote: true, method: 'get' }
+               ) do %>
+                   Run this pipeline
+                 <% end %>
+<% end %>
+
+<%= render file: 'application/show.html.erb', locals: local_assigns %>
index b05a87dd499abbac1668204a58af2ae1a37639ad..027dc29b8e045378df8a3c30b80b7ac3ca4020c2 100644 (file)
@@ -9,8 +9,10 @@
           <%= projectnode[:object] %>
         <% elsif show_root_node and rowtype == User %>
           <% if projectnode[:object].uuid == current_user.andand.uuid %>
-            <i class="fa fa-fw fa-folder-open-o"></i>
-            My Projects
+            <i class="fa fa-fw fa-folder-o"></i>
+            <%= link_to project_path(id: projectnode[:object].uuid) do %>
+              Home
+            <% end %>
           <% else %>
             <i class="fa fa-fw fa-folder-o"></i>
             <%= projectnode[:object].friendly_link_name %>
@@ -22,7 +24,7 @@
           <% end %>
         <% end %>
       </div>
-      <% if not projectnode[:object].description.blank? %>
+      <% if projectnode[:object].respond_to?(:description) and not projectnode[:object].description.blank? %>
         <div class="col-md-8 small"><%= projectnode[:object].description %></div>
       <% end %>
     </div>
index b690a1bf8f621b53556bf5480f3c536eba6e55be..e1996a7f406f2700dbfb291b07cd1d7962d6164e 100644 (file)
@@ -1,24 +1,34 @@
-<% objects_and_names.each do |object, name_link| %>
+<% get_objects_and_names.each do |object, name_link| %>
   <% name_object = (object.respond_to?(:name) || !name_link) ? object : name_link %>
   <tr class="filterable"
       data-object-uuid="<%= name_object.uuid %>"
       data-kind="<%= object.kind %>"
+      data-object-created-at="<%= object.created_at %>"
       >
     <td>
-      <%= render partial: 'selection_checkbox', locals: {object: name_object, friendly_name: ((name_object.name rescue '') || '')} %>
+      <div style="width:1em; display:inline-block;">
+        <%= render partial: 'selection_checkbox', locals: {object: name_object, friendly_name: ((name_object.name rescue '') || '')} %>
+      </div>
+    </td>
 
-      <% if project.editable? %>
-        <%= link_to({action: 'remove_item', id: project.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "Remove #{object.class_for_display.downcase} #{name_object.name rescue object.uuid} from this project?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: 'remove') do %>
+    <td>
+      <% if @object.editable? %>
+        <%= link_to({action: 'remove_item', id: @object.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "Remove #{object.class_for_display.downcase} #{name_object.name rescue object.uuid} from this project?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: 'remove') do %>
           <i class="fa fa-fw fa-trash-o"></i>
         <% end %>
       <% else %>
         <i class="fa fa-fw"></i><%# placeholder %>
       <% end %>
+    </td>
 
+    <td>
       <%= render :partial => "show_object_button", :locals => {object: object, size: 'sm', name_link: name_link} %>
+    </td>
 
+    <td>
       <%= render_editable_attribute (name_link || object), 'name', nil, {tiptitle: 'rename'} %>
     </td>
+
     <td class="arv-description-in-table">
       <%= render_controller_partial(
           'show_object_description_cell.html',
index c44db5f4bdd9b47b9ec7c88490e59def50a11153..e56321dde8a55b4137e082882b8afe77244d7e80 100644 (file)
@@ -1,65 +1,3 @@
-<% content_for :content_top do %>
-
-<h2>
-  <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => "New project" } %>
-</h2>
-
-<div class="arv-description-as-subtitle">
-  <%= render_editable_attribute @object, 'description', nil, { 'data-emptytext' => "(No description provided)", 'data-toggle' => 'manual' } %>
-</div>
-
-<% end %>
-
-<% content_for :tab_line_buttons do %>
-  <% if @object.editable? %>
-    <%= link_to(
-         choose_collections_path(
-           title: 'Add data to project:',
-           multiple: true,
-           action_name: 'Add',
-           action_href: actions_path(id: @object.uuid),
-           action_method: 'post',
-           action_data: {selection_param: 'selection[]', copy_selections_into_project: @object.uuid, success: 'page-refresh'}.to_json),
-         { class: "btn btn-primary btn-sm", remote: true, method: 'get', data: {'event-after-select' => 'page-refresh'} }) do %>
-      <i class="fa fa-fw fa-plus"></i> Add data...
-    <% end %>
-    <%= link_to(
-         choose_pipeline_templates_path(
-           title: 'Choose a pipeline to run:',
-           action_name: 'Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i>',
-           action_href: pipeline_instances_path,
-           action_method: 'post',
-           action_data: {'selection_param' => 'pipeline_instance[pipeline_template_uuid]', 'pipeline_instance[owner_uuid]' => @object.uuid, 'success' => 'redirect-to-created-object'}.to_json),
-         { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
-      <i class="fa fa-fw fa-gear"></i> Run a pipeline...
-    <% end %>
-    <%= link_to projects_path('project[owner_uuid]' => @object.uuid), method: 'post', class: 'btn btn-sm btn-primary' do %>
-      <i class="fa fa-fw fa-plus"></i>
-      Add a subproject
-    <% end %>
-    <%= link_to(
-       choose_projects_path(
-        title: 'Move this project to...',
-        editable: true,
-        my_root_selectable: true,
-        action_name: 'Move',
-        action_href: project_path(@object.uuid),
-        action_method: 'put',
-        action_data: {selection_param: 'project[owner_uuid]', success: 'page-refresh'}.to_json),
-       { class: "btn btn-sm btn-primary arv-move-to-project", remote: true, method: 'get' }) do %>
-      <i class="fa fa-fw fa-truck"></i> Move project...
-    <% end %>
-    <%= link_to(project_path(id: @object.uuid), method: 'delete', class: 'btn btn-sm btn-primary', data: {confirm: "Really delete project '#{@object.name}'?"}) do %>
-      <i class="fa fa-fw fa-trash-o"></i> Delete project
-    <% end %>
-  <% end %>
-<% end %>
-
-<% 
-  filters = [['uuid', 'is_a', "arvados#collection"]]
-  @objects = @object.contents({limit: 50, include_linked: true, :filters => filters})
-  objects_and_names = get_objects_and_names @objects
-  page_offset = next_page_offset @objects
-%>
-
-<%= render partial: 'show_tab_contents', locals: {project: @object, objects_and_names: objects_and_names, filters: filters, page_offset: page_offset, tab_name: 'Data_collections'} %>
+<%= render_pane 'tab_contents', to_string: true, locals: {
+    filters: [['uuid', 'is_a', "arvados#collection"]]
+    }.merge(local_assigns) %>
index f9e6306a9d44f01130b473442698563f35f34b56..df31fec8ee0bf19df7058fa358d0330636d06798 100644 (file)
@@ -1,8 +1,3 @@
-<%
-  filters = [['uuid', 'is_a', ["arvados#pipelineInstance","arvados#job"]]]
-  @objects = @object.contents({limit: 50, include_linked: true, :filters => filters})
-  objects_and_names = get_objects_and_names @objects
-  page_offset = next_page_offset @objects
-%>
-
-<%= render partial: 'show_tab_contents', locals: {project: @object, objects_and_names: objects_and_names, filters: filters, page_offset: page_offset, tab_name: 'Jobs_and_pipelines'} %>
+<%= render_pane 'tab_contents', to_string: true, locals: {
+    filters: [['uuid', 'is_a', ["arvados#job", "arvados#pipelineInstance"]]]
+    }.merge(local_assigns) %>
index 308034ed5429559fdab0bc73b0745ca4e5c23e45..af6fbd1a92ab9b5049e64d635741f4295e7a46a5 100644 (file)
@@ -1,8 +1,3 @@
-<%
-  filters = [['uuid', 'is_a', ["arvados#human","arvados#specimen","arvados#trait"]]]
-  @objects = @object.contents({limit: 50, include_linked: true, :filters => filters})
-  objects_and_names = get_objects_and_names @objects
-  page_offset = next_page_offset @objects
-%>
-
-<%= render partial: 'show_tab_contents', locals: {project: @object, objects_and_names: objects_and_names, filters: filters, page_offset: page_offset, tab_name: 'Other_objects'} %>
+<%= render_pane 'tab_contents', to_string: true, locals: {
+    filters: [['uuid', 'is_a', ["arvados#human", "arvados#specimen", "arvados#trait"]]]
+    }.merge(local_assigns) %>
index c54e28dfebdb9170da31e6b5913ad1c86f49d7ad..b875b086ec3e1528a1a29fb35df40ba1b239dd7c 100644 (file)
@@ -1,8 +1,3 @@
-<%
-  filters = [['uuid', 'is_a', "arvados#pipelineTemplate"]]
-  @objects = @object.contents({limit: 50, include_linked: true, :filters => filters})
-  objects_and_names = get_objects_and_names @objects
-  page_offset = next_page_offset @objects
-%>
-
-<%= render partial: 'show_tab_contents', locals: {project: @object, objects_and_names: objects_and_names, filters: filters, page_offset: page_offset, tab_name: 'Pipeline_templates'} %>
+<%= render_pane 'tab_contents', to_string: true, locals: {
+    filters: [['uuid', 'is_a', ["arvados#pipelineTemplate"]]]
+    }.merge(local_assigns) %>
index a5482efc1d3efbd0fb6d36ca48f1873b74730f9b..ff0062c24bb15b44c72e13628d44f659f2e3dff2 100644 (file)
@@ -1,7 +1,13 @@
 <%
    uuid_map = {}
-   [@users, @groups].each do |obj_list|
-     obj_list.each { |o| uuid_map[o.uuid] = o }
+   if @share_links
+     [User, Group].each do |type|
+       type.limit(10000)
+         .filter([['uuid','in',@share_links.collect(&:tail_uuid)]])
+         .each do |o|
+         uuid_map[o.uuid] = o
+       end
+     end
    end
    perm_name_desc_map = {}
    perm_desc_name_map = {}
      perms_json << {value: link_name, text: link_desc}
    end
    perms_json = perms_json.to_json
+   choose_filters = {
+     "groups" => [["group_class", "=", nil]],
+   }
+   choose_filters.default = []
    owner_icon = fa_icon_class_for_uuid(@object.owner_uuid)
    if owner_icon == "fa-users"
      owner_icon = "fa-folder"
 
   <%= link_to(send("choose_#{share_class}_path",
       title: "Share with #{share_class}",
+      by_project: false,
       preview_pane: false,
       multiple: true,
       limit: 10000,
+      filters: choose_filters[share_class].to_json,
       action_method: 'post',
       action_href: share_with_project_path,
       action_name: 'Add',
index 4497ca4ea739a9dfc180f3cc2f37697b4313a20f..2c0ba6017831b5cab5c2361a98aa5a49ed9f1e1a 100644 (file)
@@ -1,8 +1,3 @@
-<%
-  filters = [['uuid', 'is_a', "arvados#group"]]
-  @objects = @object.contents({limit: 50, include_linked: true, :filters => filters})
-  objects_and_names = get_objects_and_names @objects
-  page_offset = next_page_offset @objects
-%>
-
-<%= render partial: 'show_tab_contents', locals: {project: @object, objects_and_names: objects_and_names, filters: filters, page_offset: page_offset, tab_name: 'Subprojects'} %>
+<%= render_pane 'tab_contents', to_string: true, locals: {
+    filters: [['uuid', 'is_a', ["arvados#group"]]]
+    }.merge(local_assigns) %>
index 36f1907a3da8bf84a3fbba0bca92a8b37cd14146..e3884b629287584581fffcf57cd85e15cecc486d 100644 (file)
@@ -2,7 +2,7 @@
   <div class="row">
     <div class="col-sm-5">
       <div class="btn-group btn-group-sm">
-        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Selection... <i class="fa fa-fw fa-long-arrow-down "></i></button>
+        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Selection... <span class="caret"></span></button>
         <ul class="dropdown-menu" role="menu">
           <li><%= link_to "Compare selected", '#',
                   'data-href' => compare_pipeline_instances_path,
                   'data-selection-action' => 'remove',
                   'data-remote' => true,
                   'method' => 'delete'
+            %></li>
+                 <li><%= link_to "Move selected", '#',
+                         'data-href' => choose_projects_path(
+                                   title: 'Move to...',
+                                   editable: true,
+                                   action_name: 'Move',
+                                   action_href: move_items_project_path,
+                                   action_method: 'get',
+                                   action_data: {selection_param: 'target',
+                                                 success: 'page-refresh'}.to_json),
+                  'data-remote' => true,
+                  'data-selection-param-name' => 'move_items[]',
+                             'data-selection-action' => 'move'
             %></li>
         </ul>
       </div>
     </div>
     <div class="col-sm-4 pull-right">
-      <input type="text" class="form-control filterable-control" placeholder="Search project contents" data-filterable-target="table.arv-index.arv-project-<%= tab_name %> tbody"/>
+      <input type="text" class="form-control filterable-control" placeholder="Search project contents" data-filterable-target="table.arv-index.arv-project-<%= tab_pane %> tbody"/>
     </div>
   </div>
 
-  <table class="table table-condensed table-fixedlayout arv-index arv-project-<%= tab_name %>" style="overflow-x: hidden">
+  <table class="table table-condensed arv-index arv-project-<%= tab_pane %>">
     <colgroup>
-      <col width="40%" />
-      <col width="60%" />
+      <col width="0*" style="max-width: fit-content;" />
+      <col width="0*" style="max-width: fit-content;" />
+      <col width="0*" style="max-width: fit-content;" />
+      <col width="60%" style="width: 60%;" />
+      <col width="40%" style="width: 40%;" />
     </colgroup>
-    <tbody data-infinite-scroller="#<%= tab_name %>-scroll" data-infinite-content-href="<%= url_for(format: :json, partial: :contents_rows, offset: page_offset, filters: "#{filters}") if page_offset %>">
-      <%= render partial: 'show_contents_rows', locals: {project: @object, objects_and_names: objects_and_names} %>
+    <tbody data-infinite-scroller="#<%= tab_pane %>-scroll" data-infinite-content-href="<%= url_for partial: :contents_rows, filters: filters.to_json %>">
     </tbody>
     <thead>
       <tr>
-        <th>
-        </th>
-        <th>
-          description
-        </th>
+        <th></th>
+        <th></th>
+        <th></th>
+        <th>name</th>
+        <th>description</th>
       </tr>
     </thead>
   </table>
-
 </div>
index 2c764335f6b7c1911180589fbe530b9b6c8fa4c0..219bad262b59ddac55972c20680887e563e97fe6 100644 (file)
@@ -1,8 +1,3 @@
-<% content_for :breadcrumbs do %>
-<li class="nav-separator"><span class="glyphicon glyphicon-arrow-right"></span></li>
-<li><a href="#">Home</a></li>
-<% end %>
-
 <div>
   <div class="row">
     <div class="col-sm-6">
@@ -37,7 +32,7 @@
           </h3>
         </div>
         <div class="panel-body scroll-20em">
-          <%= render partial: 'index_projects', locals: {tree: my_project_tree, show_root_node: false} %>
+          <%= render partial: 'index_projects', locals: {tree: my_project_tree, show_root_node: true} %>
         </div>
       </div>
     </div>
diff --git a/apps/workbench/app/views/projects/show.html.erb b/apps/workbench/app/views/projects/show.html.erb
new file mode 100644 (file)
index 0000000..c221ca1
--- /dev/null
@@ -0,0 +1,62 @@
+<% if @object.uuid != current_user.uuid # Not the "Home" project %>
+<% content_for :content_top do %>
+
+<h2>
+  <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => "New project" } %>
+</h2>
+
+<div class="arv-description-as-subtitle">
+  <%= render_editable_attribute @object, 'description', nil, { 'data-emptytext' => "(No description provided)", 'data-toggle' => 'manual' } %>
+</div>
+
+<% end %>
+<% end %>
+
+<% content_for :tab_line_buttons do %>
+  <% if @object.editable? %>
+    <%= link_to(
+         choose_collections_path(
+           title: 'Add data to project:',
+           multiple: true,
+           action_name: 'Add',
+           action_href: actions_path(id: @object.uuid),
+           action_method: 'post',
+           action_data: {selection_param: 'selection[]', copy_selections_into_project: @object.uuid, success: 'page-refresh'}.to_json),
+         { class: "btn btn-primary btn-sm", remote: true, method: 'get', data: {'event-after-select' => 'page-refresh'} }) do %>
+      <i class="fa fa-fw fa-plus"></i> Add data...
+    <% end %>
+    <%= link_to(
+         choose_pipeline_templates_path(
+           title: 'Choose a pipeline to run:',
+           action_name: 'Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i>',
+           action_href: pipeline_instances_path,
+           action_method: 'post',
+           action_data: {'selection_param' => 'pipeline_instance[pipeline_template_uuid]', 'pipeline_instance[owner_uuid]' => @object.uuid, 'success' => 'redirect-to-created-object'}.to_json),
+         { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
+      <i class="fa fa-fw fa-gear"></i> Run a pipeline...
+    <% end %>
+    <%= link_to projects_path('project[owner_uuid]' => @object.uuid), method: 'post', class: 'btn btn-sm btn-primary' do %>
+      <i class="fa fa-fw fa-plus"></i>
+      Add a subproject
+    <% end %>
+    <% if @object.uuid != current_user.uuid # Not the "Home" project %>
+      <%= link_to(
+          choose_projects_path(
+           title: 'Move this project to...',
+           editable: true,
+           my_root_selectable: true,
+           action_name: 'Move',
+           action_href: project_path(@object.uuid),
+           action_method: 'put',
+           action_data: {selection_param: 'project[owner_uuid]', success: 'page-refresh'}.to_json),
+          { class: "btn btn-sm btn-primary arv-move-to-project", remote: true, method: 'get' }) do %>
+        <i class="fa fa-fw fa-truck"></i> Move project...
+      <% end %>
+      <%= link_to(project_path(id: @object.uuid), method: 'delete', class: 'btn btn-sm btn-primary', data: {confirm: "Really delete project '#{@object.name}'?"}) do %>
+        <i class="fa fa-fw fa-trash-o"></i> Delete project
+      <% end %>
+    <% end %>
+  <% end %>
+<% end %>
+
+<%= render file: 'application/show.html.erb', locals: local_assigns %>
diff --git a/apps/workbench/app/views/search/_choose_rows.html.erb b/apps/workbench/app/views/search/_choose_rows.html.erb
new file mode 100644 (file)
index 0000000..61f9300
--- /dev/null
@@ -0,0 +1,26 @@
+<% current_class = params[:last_object_class] %>
+<% @objects.each do |object| %>
+  <% icon_class = fa_icon_class_for_class(object.class) %>
+  <% if object.class.to_s != current_class %>
+    <% current_class = object.class.to_s %>
+    <div class="row class-separator" data-section-heading="true" data-section-name="<%= object.class.to_s %>">
+      <div class="col-sm-12">
+        <%= object.class_for_display.pluralize.downcase %>
+      </div>
+    </div>
+  <% end %>
+  <div class="row filterable selectable" data-section-name="<%= object.class.to_s %>" data-object-uuid="<%= object.uuid %>" data-preview-href="<%= chooser_preview_url_for object %>">
+    <div class="col-sm-12" style="overflow-x:hidden; white-space: nowrap">
+      <i class="fa fa-fw <%= icon_class %>"></i>
+      <% if (name_link = @objects.links_for(object, 'name').first) %>
+        <%= name_link.name %>
+        <span style="display:none"><%= object.uuid %></span>
+      <% elsif object.respond_to?(:name) and object.name and object.name.length > 0 %>
+        <%= object.name %>
+        <span style="display:none"><%= object.uuid %></span>
+      <% else %>
+        <span class="arvados-uuid"><%= object.uuid %></span>
+      <% end %>
+    </div>
+  </div>
+<% end %>
diff --git a/apps/workbench/app/views/users/_add_ssh_key_popup.html.erb b/apps/workbench/app/views/users/_add_ssh_key_popup.html.erb
new file mode 100644 (file)
index 0000000..efa8cae
--- /dev/null
@@ -0,0 +1,38 @@
+<div class="modal-dialog modal-with-loading-spinner">
+  <div class="modal-content">
+
+    <%= form_tag add_ssh_key_path, {method: 'get', id: 'add_new_key_form', name: 'add_new_key_form', class: 'form-search, new_authorized_key', remote: true} do %>
+
+      <div class="modal-header">
+        <button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">&times;</button>
+        <div>
+          <div class="col-sm-6"> <h4 class="modal-title">Add SSH Key</h4> </div>
+          <div class="spinner spinner-32px spinner-h-center col-sm-1" hidden="true"></div>
+        </div>
+        <br/>
+      </div>
+
+      <div class="modal-body">
+        <div> <%= link_to "Click here to learn about SSH keys in Arvados.",
+                 "#{Rails.configuration.arvados_docsite}/user/getting_started/ssh-access-unix.html", 
+                 style: "font-weight: bold",
+                 target: "_blank" %>
+        </div>
+        <div class="form-group">
+          <label for="public_key">Public Key</label>
+          <textarea class="form-control" id="public_key" rows="4" name="public_key" type="text"/>
+        </div>
+        <div class="form-group">
+          <label for="name">Name</label>
+          <input class="form-control" id="name" maxlength="250" name="name" type="text"/>
+        </div>
+      </div>
+
+      <div class="modal-footer">
+        <button type="submit" class="btn btn-primary" autofocus>Submit</button>
+        <button class="btn btn-default" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">Cancel</button>
+      </div>
+
+    <% end #form %>
+  </div>
+</div>
index a893e5e6d1e35ec7fc1a65f9b830d8bcf185e6f3..f32d4b9aaf01e87391b0ec3ed2290e9e43f14b6d 100644 (file)
@@ -1,9 +1,9 @@
 <% icon_class = fa_icon_class_for_class(User) %>
 <% @objects.each do |object| %>
-  <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+  <div class="row filterable selectable" data-object-uuid="<%= object.uuid %>">
     <div class="col-sm-12" style="overflow-x:hidden">
       <i class="fa fa-fw <%= icon_class %>"></i>
-      <%= object.full_name %>
+      <%= object.full_name %> &lt;<%= object.email %>&gt;
     </div>
   </div>
 <% end %>
diff --git a/apps/workbench/app/views/users/_manage_account.html.erb b/apps/workbench/app/views/users/_manage_account.html.erb
new file mode 100644 (file)
index 0000000..5024fce
--- /dev/null
@@ -0,0 +1,52 @@
+<div class="col-sm-6">
+  <div class="panel-group" id="arv-adv-accordion">
+    <% ['Virtual Machines',
+       'Repositories'].each do |section| %>
+      <% section_id = section.gsub(" ","_").downcase %>
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            <a data-parent="#arv-adv-accordion" href="#manage_<%=section_id%>">
+              <%= section %>
+            </a>
+          </h4>
+        </div>
+        <div id="manage_<%=section_id%>">
+          <div class="panel-body">
+            <%= render partial: "manage_#{section_id}" %>
+          </div>
+        </div>
+      </div>
+    <% end %>
+  </div>
+</div>
+<div class="col-sm-6">
+  <div class="panel-group" id="arv-adv-accordion">
+    <% ['SSH Keys',
+      'Current Token'].each do |section| %>
+      <% section_id = section.gsub(" ","_").downcase %>
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <% if section_id == 'ssh_keys' %>
+            <div class="pull-right">
+              <%= link_to raw('<i class="fa fa-plus"></i>' " Add new SSH key"), add_ssh_key_popup_url,
+                           {class: 'btn btn-xs btn-primary', :remote => true, 'data-toggle' =>  "modal",
+                            'data-target' => '#add-ssh-key-modal-window'}  %>
+            </div>
+          <% end %>
+          <h4 class="panel-title">
+            <a data-parent="#arv-adv-accordion" href="#manage_<%=section_id%>">
+              <%= section %>
+            </a>
+          </h4>
+        </div>
+        <div id="manage_<%=section_id%>">
+          <div class="panel-body">
+            <%= render partial: "manage_#{section_id}" %>
+          </div>
+        </div>
+      </div>
+    <% end %>
+  </div>
+  <div id="add-ssh-key-modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"></div>
+</div>
diff --git a/apps/workbench/app/views/users/_manage_current_token.html.erb b/apps/workbench/app/views/users/_manage_current_token.html.erb
new file mode 100644 (file)
index 0000000..71c6bd2
--- /dev/null
@@ -0,0 +1,13 @@
+<p>The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados with the proper permissions. For more information see <%= link_to raw('Getting an API token'), "#{Rails.configuration.arvados_docsite}/user/reference/api-tokens.html", target: "_blank"%>.</p>
+<p>Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your account, <b><%= current_user.email %></b></p>
+
+<pre>
+HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
+export ARVADOS_API_TOKEN=<%= Thread.current[:arvados_api_token] %>
+export ARVADOS_API_HOST=<%= current_api_host %>
+<% if Rails.configuration.arvados_insecure_https %>
+export ARVADOS_API_HOST_INSECURE=true
+<% else %>
+unset ARVADOS_API_HOST_INSECURE
+<% end %>
+</pre>
diff --git a/apps/workbench/app/views/users/_manage_repositories.html.erb b/apps/workbench/app/views/users/_manage_repositories.html.erb
new file mode 100644 (file)
index 0000000..4f22ae9
--- /dev/null
@@ -0,0 +1,40 @@
+<div>
+  <p>
+    For more information see <%= link_to raw('Writing a pipeline'),
+    "#{Rails.configuration.arvados_docsite}/user/tutorials/tutorial-firstscript.html", target: "_blank"%>.
+  </p>
+
+  <% if !@my_repositories.any? %>
+    You do not seem to have access to any repositories. If you would like to request access, please contact your system admin.
+  <% else %>
+    <table class="table repositories-table">
+      <colgroup>
+        <col style="width: 30%" />
+        <col style="width: 10%" />
+        <col style="width: 60%" />
+      </colgroup>
+      <thead>
+        <tr>
+          <th> Name </th>
+          <th> Read/Write </th>
+          <th> URL </th>
+        </tr>
+      </thead>
+      <tbody>
+        <% @my_repositories.andand.each do |repo| %>
+          <tr>
+            <td style="word-break:break-all;">
+              <%= repo[:name] %>
+            </td>
+            <td>
+              <%= repo[:push_url] ? 'writable' : 'read-only' %>
+            </td>
+            <td style="word-break:break-all;">
+              <code><%= repo[:fetch_url] %></code>
+            </td>
+          </tr>
+        <% end %>
+      </tbody>
+    </table>
+  <% end %>
+</div>
diff --git a/apps/workbench/app/views/users/_manage_ssh_keys.html.erb b/apps/workbench/app/views/users/_manage_ssh_keys.html.erb
new file mode 100644 (file)
index 0000000..17b64b2
--- /dev/null
@@ -0,0 +1,55 @@
+<div>
+  <% if !@my_ssh_keys.any? %>
+     <p> You have not yet set up an SSH public key for use with Arvados. </p>
+     <p>  <%= link_to "Click here",
+                 "#{Rails.configuration.arvados_docsite}/user/getting_started/ssh-access-unix.html", 
+                 style: "font-weight: bold",
+                 target: "_blank" %>  to learn about SSH keys in Arvados.
+     </p>
+     <p> When you have an SSH key you would like to use, add it using the <b>Add</b> button. </p>
+  <% else %>
+    <table class="table manage-ssh-keys-table">
+      <colgroup>
+        <col style="width: 35%" />
+        <col style="width: 55%" />
+        <col style="width: 10%" />
+      </colgroup>
+      <thead>
+        <tr>
+          <th> Name </th>
+          <th> Key Fingerprint </th>
+          <th> </th>
+        </tr>
+      </thead>
+      <tbody>
+        <% @my_ssh_keys.andand.each do |key| %>
+          <tr style="word-break:break-all;">
+            <td>
+              <%= key[:name] %>
+            </td>
+            <td style="word-break:break-all;">
+              <% if key[:public_key] && key[:public_key].size > 0 %>
+                <div>
+                  <span title="<%=key[:public_key]%>"> <%=
+                    begin
+                      SSHKey.fingerprint key[:public_key]
+                    rescue
+                      "INVALID KEY: " + key[:public_key]
+                    end
+                   %> </span>
+                </div>
+              <% else %>
+                  <%= key[:public_key] %>
+              <% end %>
+            </td>
+            <td>
+              <%= link_to(authorized_key_path(id: key[:uuid]), method: 'delete', class: 'btn btn-sm', data: {confirm: "Really delete key?"}) do %>
+                  <i class="fa fa-fw fa-trash-o"></i>
+              <% end %>
+            </td>
+          </tr>
+        <% end %>
+      </tbody>
+    </table>
+  <% end %>
+</div>
diff --git a/apps/workbench/app/views/users/_manage_virtual_machines.html.erb b/apps/workbench/app/views/users/_manage_virtual_machines.html.erb
new file mode 100644 (file)
index 0000000..36cab18
--- /dev/null
@@ -0,0 +1,50 @@
+<div>
+  <p>
+    For more information see <%= link_to raw('Arvados Docs &rarr; User Guide &rarr; SSH access'),
+  "#{Rails.configuration.arvados_docsite}/user/getting_started/ssh-access-unix.html",
+  target: "_blank"%>.  A sample <i>~/.ssh/config</i> entry is provided below.
+  </p>
+
+  <% if !@my_virtual_machines.any? %>
+    You do not seem to have access to any virtual machines. If you would like to request access, please contact your system admin.
+  <% else %>
+    <table class="table virtual-machines-table">
+      <colgroup>
+        <col style="width: 25%" />
+        <col style="width: 25%" />
+        <col style="width: 50%" />
+      </colgroup>
+      <thead>
+        <tr>
+          <th> Host name </th>
+          <th> Login name </th>
+          <th> Command line </th>
+        </tr>
+      </thead>
+      <tbody>
+        <% @my_virtual_machines.andand.each do |vm| %>
+          <tr>
+            <td style="word-break:break-all;">
+              <%= vm[:hostname] %>
+            </td>
+            <td style="word-break:break-all;">
+              <%= @my_vm_logins[vm[:uuid]].andand.compact.andand.join(", ") %>
+            </td>
+            <td style="word-break:break-all;">
+              <% if @my_vm_logins[vm[:uuid]] %>
+                <% @my_vm_logins[vm[:uuid]].each do |login| %>
+                  <code>ssh&nbsp;<%= login %>@<%= vm[:hostname] %>.arvados</code>
+                <% end %>
+              <% end %>
+            </td>
+          </tr>
+        <% end %>
+      </tbody>
+    </table>
+
+    <p><i>~/.ssh/config:</i></p>
+    <pre>Host *.arvados
+      ProxyCommand ssh -p2222 turnout@switchyard.<%= current_api_host || 'xyzzy.arvadosapi.com' %> -x -a $SSH_PROXY_FLAGS %h
+    </pre>
+  <% end %>
+</div>
index 9d67f9480360168649f36a02e03230a7469994a6..e9429cf451d43b8c48e601be3d7cb97824fa8adf 100644 (file)
@@ -44,7 +44,7 @@
         <% end %>
       </div>
       <div class="form-group">
-        <label for="repo_name">Repository Name</label>
+        <label for="repo_name">Repository Name and Shell Login</label>
         <input class="form-control" id="repo_name" maxlength="250" name="repo_name" type="text" value="<%=selected_repo%>">
       </div>
       <div class="form-group">
diff --git a/apps/workbench/app/views/users/add_ssh_key.js.erb b/apps/workbench/app/views/users/add_ssh_key.js.erb
new file mode 100644 (file)
index 0000000..6117b98
--- /dev/null
@@ -0,0 +1,2 @@
+$("#add-ssh-key-modal-window").modal("hide");
+document.location.reload();
diff --git a/apps/workbench/app/views/users/add_ssh_key_popup.js.erb b/apps/workbench/app/views/users/add_ssh_key_popup.js.erb
new file mode 100644 (file)
index 0000000..b04d37f
--- /dev/null
@@ -0,0 +1,8 @@
+$("#add-ssh-key-modal-window").html("<%= escape_javascript(render partial: 'add_ssh_key_popup') %>");
+
+// reset form input fields, for the next time around
+function reset_form() {
+  $('#name').val("");
+  $('#public_key').val("");
+  $('select').val('')
+}
diff --git a/apps/workbench/app/views/users/manage_account.html.erb b/apps/workbench/app/views/users/manage_account.html.erb
new file mode 100644 (file)
index 0000000..0751866
--- /dev/null
@@ -0,0 +1 @@
+<%= render :partial => 'manage_account' %>
diff --git a/apps/workbench/app/views/users/profile.html.erb b/apps/workbench/app/views/users/profile.html.erb
new file mode 100644 (file)
index 0000000..2c40fef
--- /dev/null
@@ -0,0 +1,108 @@
+<%
+    profile_config = Rails.configuration.user_profile_form_fields
+    current_user_profile = current_user.prefs[:profile]
+    show_save_button = false
+
+    profile_message = Rails.configuration.user_profile_form_message ? Rails.configuration.user_profile_form_message : 'You can manage your profile using this page. Any feilds in red are required and missing. Please fill in those fields before you can accesse Arvados Workbench.'
+
+    missing_required = missing_required_profile?
+
+    profile_url = '/users/'+current_user.uuid+'/profile'
+    target = request.url.partition('?target=')[-1]
+    target = request.url if target.empty?
+    return_to_url = (request.url.ends_with? profile_url) ? profile_url : profile_url+'?target='+target
+%>
+
+<div>
+    <div class="panel panel-default">
+        <div class="panel-heading">
+          <h4 class="panel-title">
+            Profile
+          </h4>
+        </div>
+        <div class="panel-body">
+          <% if !missing_required && params.andand.keys.include?('target') %>
+            <div class="rounded" style="border-width: 1px; border-style: dotted; border-color: lightgray;">
+              <p style="margin: 8px;">Thank you for filling in your profile. If you are done updating your profile,
+                 you can now access Arvados Workbench by clicking on this button.
+                  <form action="<%=target%>">
+                    <input style="margin-left: 8px;" class="btn btn-primary" type="submit" value="Access Arvados Workbench">
+                  </form>
+              </p>
+            </div>
+          <% else %>
+            <div class="rounded" style="border-width: 1px; border-style: dotted; border-color: lightgray;">
+              <p style="margin: 8px;"> <%=raw(profile_message)%> </p>
+            </div>
+          <% end %>
+
+          <div class="rounded" style="border-width: 1px; border-style: dotted; border-color: lightgray;">
+            <%= form_tag "/users/#{current_user.uuid}", {method: 'patch', id: 'save_profile_form', name: 'save_profile_form', class: 'form-horizontal'} do %>
+              <%= hidden_field_tag :return_to, return_to_url %>
+              <div class="form-group">
+                  <label for="email" class="col-sm-3 control-label"> Email </label>
+                  <div class="col-sm-8">
+                    <p class="form-control-static" id="email" name="email"><%=current_user.email%></p>
+                  </div>
+              </div>
+              <div class="form-group">
+                  <label for="first_name" class="col-sm-3 control-label"> First name </label>
+                  <div class="col-sm-8">
+                    <p class="form-control-static" id="first_name" name="first_name"><%=current_user.first_name%></p>
+                  </div>
+              </div>
+              <div class="form-group">
+                  <label for="last_name" class="col-sm-3 control-label"> Last name </label>
+                  <div class="col-sm-8">
+                    <p class="form-control-static" id="last_name" name="last_name"><%=current_user.last_name%></p>
+                  </div>
+              </div>
+              <div class="form-group">
+                  <label for="identity_url" class="col-sm-3 control-label"> Identity URL </label>
+                  <div class="col-sm-8">
+                    <p class="form-control-static" id="identity_url" name="identity_url"><%=current_user.andand.identity_url%></p>
+                  </div>
+              </div>
+
+              <% profile_config.kind_of?(Array) && profile_config.andand.each do |entry| %>
+                <% if entry['key'] %>
+                  <%
+                      show_save_button = true
+                      label = entry['required'] ? '* ' : ''
+                      label += entry['form_field_title']
+                      value = current_user_profile[entry['key'].to_sym] if current_user_profile
+                  %>
+                  <div class="form-group">
+                    <label for="<%=entry['key']%>"
+                           class="col-sm-3 control-label"
+                           style=<%="color:red" if entry['required']&&(!value||value.empty?)%>> <%=label%>
+                    </label>
+                    <% if entry['type'] == 'select' %>
+                      <div class="col-sm-8">
+                        <select class="form-control" name="user[prefs][:profile][:<%=entry['key']%>]">
+                          <% entry['options'].each do |option| %>
+                            <option value="<%=option%>" <%='selected' if option==value%>><%=option%></option>
+                          <% end %>
+                        </select>
+                      </div>
+                    <% else %>
+                      <div class="col-sm-8">
+                        <input type="text" class="form-control" name="user[prefs][:profile][:<%=entry['key']%>]" placeholder="<%=entry['form_field_description']%>" value="<%=value%>" ></input>
+                      </div>
+                    <% end %>
+                  </div>
+                <% end %>
+              <% end %>
+
+              <% if show_save_button %>
+                <div class="form-group">
+                  <div class="col-sm-offset-3 col-sm-8">
+                    <button type="submit" class="btn btn-primary">Save profile</button>
+                  </div>
+                </div>
+              <% end %>
+            <% end %>
+          </div>
+        </div>
+    </div>
+</div>
index 2fe701afcb6d5efdd9b92abba0ef94f571f23058..3b4c2c0684bdb6cff3795776bc60b5588d2ec3ec 100644 (file)
@@ -58,6 +58,25 @@ test:
 
   site_name: Workbench:test
 
+  # Enable user profile with one required field
+  user_profile_form_fields:
+    - key: organization
+      type: text
+      form_field_title: Institution
+      form_field_description: Your organization
+      required: true
+    - key: role
+      type: select
+      form_field_title: Your role
+      form_field_description: Choose the category that best describes your role in your organization.
+      options:
+        - Bio-informatician
+        - Computational biologist
+        - Biologist or geneticist
+        - Software developer
+        - IT
+        - Other
+
 common:
   assets.js_compressor: false
   assets.css_compressor: false
@@ -74,3 +93,41 @@ common:
   secret_key_base: false
   default_openid_prefix: https://www.google.com/accounts/o8/id
   send_user_setup_notification_email: true
+
+  # Set user_profile_form_fields to enable and configure the user profile page.
+  # Default is set to false. A commented setting with full description is provided below.
+  user_profile_form_fields: false
+
+  # Below is a sample setting of user_profile_form_fields config parameter.
+  # This configuration parameter should be set to either false (to disable) or
+  # to an array as shown below. 
+  # Configure the list of input fields to be displayed in the profile page
+  # using the attribute "key" for each of the input fields.
+  # This sample shows configuration with one required and one optional form fields.
+  # For each of these input fields:
+  #   You can specify "type" as "text" or "select".
+  #   List the "options" to be displayed for each of the "select" menu.
+  #   Set "required" as "true" for any of these fields to make them required.
+  # If any of the required fields are missing in the user's profile, the user will be
+  # redirected to the profile page before they can access any Workbench features.
+  #user_profile_form_fields:
+  #  - key: organization
+  #    type: text
+  #    form_field_title: Institution/Company
+  #    form_field_description: Your organization
+  #    required: true
+  #  - key: role
+  #    type: select
+  #    form_field_title: Your role
+  #    form_field_description: Choose the category that best describes your role in your organization.
+  #    options:
+  #      - Bio-informatician
+  #      - Computational biologist
+  #      - Biologist or geneticist
+  #      - Software developer
+  #      - IT
+  #      - Other
+
+  # Use "user_profile_form_message" to configure the message you want to display in
+  # the profile page. If this is not provided, a default message will be displayed.
+  user_profile_form_message: Welcome to Arvados. Please fill in all required fields before you can access Arvados Workbench. Missing required fields are in <span style="color:red">red</span>.
index 395f1a94bd470e540b4c69fcb4c32c97896a6526..f617af35ce55fbe1b8945fb521aedc7243a4dff7 100644 (file)
@@ -10,7 +10,7 @@
 # 4. Section in application.default.yml corresponding to RAILS_ENV
 # 5. Section in application.default.yml called "common"
 
-common:
+development:
   # At minimum, you need a nice long randomly generated secret_token here.
   secret_token: ~
 
index 55e2fdae364465ef07958d728e9b992b26fffa5a..4ef26612ab194a2df62eefa3945aa1ab75ab5c4e 100644 (file)
@@ -32,7 +32,11 @@ ArvadosWorkbench::Application.routes.draw do
     post 'sudo', :on => :member
     post 'unsetup', :on => :member
     get 'setup_popup', :on => :member
+    get 'profile', :on => :member
   end
+  get '/manage_account' => 'users#manage_account'
+  get "/add_ssh_key_popup" => 'users#add_ssh_key_popup', :as => :add_ssh_key_popup
+  get "/add_ssh_key" => 'users#add_ssh_key', :as => :add_ssh_key
   resources :logs
   resources :factory_jobs
   resources :uploaded_datasets
@@ -63,11 +67,16 @@ ArvadosWorkbench::Application.routes.draw do
   resources :projects do
     match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
     match 'remove_items', on: :member, via: :delete, action: :remove_items
+    get 'move_items', on: :member, action: :move_items
     get 'choose', on: :collection
     post 'share_with', on: :member
   end
+  resources :search do
+    get 'choose', :on => :collection
+  end
 
   post 'actions' => 'actions#post'
+  get 'actions' => 'actions#show'
   get 'websockets' => 'websocket#index'
 
   root :to => 'projects#index'
diff --git a/apps/workbench/test/controllers/search_controller_test.rb b/apps/workbench/test/controllers/search_controller_test.rb
new file mode 100644 (file)
index 0000000..bfbf22d
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class SearchControllerTest < ActionController::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
index 39c32ac4ac3d959bc31dc3b91bdffd87af1b440f..ae0cad09e3917213d5f5a266370aed68666f95b0 100644 (file)
@@ -38,7 +38,6 @@ class ProjectsControllerTest < ActionController::TestCase
            format: "json"},
          session_for(:active))
     assert_response :success
-    json_response = Oj.load(@response.body)
     assert_equal(uuid_list, json_response["success"])
   end
 
@@ -50,7 +49,6 @@ class ProjectsControllerTest < ActionController::TestCase
            format: "json"},
          session_for(:project_viewer))
     assert_response 422
-    json_response = Oj.load(@response.body)
     assert(json_response["errors"].andand.
              any? { |msg| msg.start_with?("#{share_uuid}: ") },
            "JSON response missing properly formatted sharing error")
@@ -87,4 +85,74 @@ class ProjectsControllerTest < ActionController::TestCase
   test "viewer can't manage asubproject" do
     refute user_can_manage(:project_viewer, "asubproject")
   end
+
+  test 'projects#show tab infinite scroll partial obeys limit' do
+    get_contents_rows(limit: 1, filters: [['uuid','is_a',['arvados#job']]])
+    assert_response :success
+    assert_equal(1, json_response['content'].scan('<tr').count,
+                 "Did not get exactly one row")
+  end
+
+  ['', ' asc', ' desc'].each do |direction|
+    test "projects#show tab partial orders correctly by #{direction}" do
+      _test_tab_content_order direction
+    end
+  end
+
+  def _test_tab_content_order direction
+    get_contents_rows(limit: 100,
+                      order: "created_at#{direction}",
+                      filters: [['uuid','is_a',['arvados#job',
+                                                'arvados#pipelineInstance']]])
+    assert_response :success
+    not_grouped_by_kind = nil
+    last_timestamp = nil
+    last_kind = nil
+    found_kind = {}
+    json_response['content'].scan /<tr[^>]+>/ do |tr_tag|
+      found_timestamps = 0
+      tr_tag.scan(/\ data-object-created-at=\"(.*?)\"/).each do |t,|
+        if last_timestamp
+          correct_operator = / desc$/ =~ direction ? :>= : :<=
+          assert_operator(last_timestamp, correct_operator, t,
+                          "Rows are not sorted by created_at#{direction}")
+        end
+        last_timestamp = t
+        found_timestamps += 1
+      end
+      assert_equal(1, found_timestamps,
+                   "Content row did not have exactly one timestamp")
+
+      # Confirm that the test for timestamp ordering couldn't have
+      # passed merely because the test fixtures have convenient
+      # timestamps (e.g., there is only one pipeline and one job in
+      # the project being tested, or there are no pipelines at all in
+      # the project being tested):
+      tr_tag.scan /\ data-kind=\"(.*?)\"/ do |kind|
+        if last_kind and last_kind != kind and found_kind[kind]
+          # We saw this kind before, then a different kind, then
+          # this kind again. That means objects are not grouped by
+          # kind.
+          not_grouped_by_kind = true
+        end
+        found_kind[kind] ||= 0
+        found_kind[kind] += 1
+        last_kind = kind
+      end
+    end
+    assert_equal(true, not_grouped_by_kind,
+                 "Could not confirm that results are not grouped by kind")
+  end
+
+  def get_contents_rows params
+    params = {
+      id: api_fixture('users')['active']['uuid'],
+      partial: :contents_rows,
+      format: :json,
+    }.merge(params)
+    encoded_params = Hash[params.map { |k,v|
+                            [k, (v.is_a?(Array) || v.is_a?(Hash)) ? v.to_json : v]
+                          }]
+    get :show, encoded_params, session_for(:active)
+  end
 end
diff --git a/apps/workbench/test/helpers/search_helper_test.rb b/apps/workbench/test/helpers/search_helper_test.rb
new file mode 100644 (file)
index 0000000..3034163
--- /dev/null
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class SearchHelperTest < ActionView::TestCase
+end
diff --git a/apps/workbench/test/integration/application_layout_test.rb b/apps/workbench/test/integration/application_layout_test.rb
new file mode 100644 (file)
index 0000000..8a0ebc3
--- /dev/null
@@ -0,0 +1,317 @@
+require 'integration_helper'
+require 'selenium-webdriver'
+require 'headless'
+
+class ApplicationLayoutTest < ActionDispatch::IntegrationTest
+  setup do
+    headless = Headless.new
+    headless.start
+    Capybara.current_driver = :selenium
+
+    @user_profile_form_fields = Rails.configuration.user_profile_form_fields
+  end
+
+  teardown do
+    Rails.configuration.user_profile_form_fields = @user_profile_form_fields
+  end
+
+  def verify_homepage_with_profile user, invited, has_profile
+    profile_config = Rails.configuration.user_profile_form_fields
+
+    if !user
+      assert page.has_text? 'Please log in'
+      assert page.has_text? 'The "Log in" button below will show you a Google sign-in page'
+      assert page.has_no_text? 'My projects'
+      assert page.has_link? "Log in to #{Rails.configuration.site_name}"
+    elsif profile_config && !has_profile && user['is_active']
+      add_profile user
+    elsif user['is_active']
+      assert page.has_text? 'My projects'
+      assert page.has_text? 'Projects shared with me'
+      assert page.has_no_text? 'Save profile'
+    elsif invited
+      assert page.has_text? 'Please check the box below to indicate that you have read and accepted the user agreement'
+      assert page.has_no_text? 'Save profile'
+    else
+      assert page.has_text? 'Your account is inactive'
+      assert page.has_no_text? 'Save profile'
+    end
+
+    within('.navbar-fixed-top') do
+      if !user
+        assert page.has_link? 'Log in'
+      else
+        # my account menu
+        assert page.has_link? "#{user['email']}"
+        find('a', text: "#{user['email']}").click
+        within('.dropdown-menu') do
+          if user['is_active']
+            assert page.has_no_link? ('Not active')
+            assert page.has_no_link? ('Sign agreements')
+
+            assert page.has_link? ('Manage account')
+
+            if profile_config
+              assert page.has_link? ('Manage profile')
+            else
+              assert page.has_no_link? ('Manage profile')
+            end
+          end
+          assert page.has_link? ('Log out')
+        end
+      end
+    end
+  end
+
+  # test the help menu
+  def check_help_menu
+    within('.navbar-fixed-top') do
+      page.find("#arv-help").click
+      within('.dropdown-menu') do
+        assert page.has_link? 'Tutorials and User guide'
+        assert page.has_link? 'API Reference'
+        assert page.has_link? 'SDK Reference'
+      end
+    end
+  end
+
+  def verify_system_menu user
+    if user && user['is_active']
+      look_for_add_new = nil
+      within('.navbar-fixed-top') do
+        page.find("#system-menu").click
+        if user['is_admin']
+          within('.dropdown-menu') do
+            assert page.has_text? 'Groups'
+            assert page.has_link? 'Repositories'
+            assert page.has_link? 'Virtual machines'
+            assert page.has_link? 'SSH keys'
+            assert page.has_link? 'API tokens'
+            find('a', text: 'Users').click
+            look_for_add_new = 'Add a new user'
+          end
+        else
+          within('.dropdown-menu') do
+            assert page.has_no_text? 'Users'
+            assert page.has_no_link? 'Repositories'
+            assert page.has_no_link? 'Virtual machines'
+            assert page.has_no_link? 'SSH keys'
+            assert page.has_no_link? 'API tokens'
+
+            find('a', text: 'Groups').click
+            look_for_add_new = 'Add a new group'
+          end
+        end
+      end
+      if look_for_add_new
+        assert page.has_text? look_for_add_new
+      end
+    else
+      assert page.has_no_link? '#system-menu'
+    end
+  end
+
+  # test manage_account page
+  def verify_manage_account user
+    if user && user['is_active']
+      within('.navbar-fixed-top') do
+        find('a', text: "#{user['email']}").click
+        within('.dropdown-menu') do
+          find('a', text: 'Manage account').click
+        end
+      end
+
+      # now in manage account page
+      assert page.has_text? 'Virtual Machines'
+      assert page.has_text? 'Repositories'
+      assert page.has_text? 'SSH Keys'
+      assert page.has_text? 'Current Token'
+
+      assert page.has_text? 'The Arvados API token is a secret key that enables the Arvados SDKs to access Arvados'
+
+      click_link 'Add new SSH key'
+
+      within '.modal-content' do
+        assert page.has_text? 'Public Key'
+        assert page.has_button? 'Cancel'
+        assert page.has_button? 'Submit'
+
+        page.find_field('public_key').set 'first test with an incorrect ssh key value'
+        click_button 'Submit'
+        assert page.has_text? 'Public key does not appear to be a valid ssh-rsa or dsa public key'
+
+        public_key_str = api_fixture('authorized_keys')['active']['public_key']
+        page.find_field('public_key').set public_key_str
+        page.find_field('name').set 'added_in_test'
+        click_button 'Submit'
+        assert page.has_text? 'Public key already exists in the database, use a different key.'
+
+        new_key = SSHKey.generate
+        page.find_field('public_key').set new_key.ssh_public_key
+        page.find_field('name').set 'added_in_test'
+        click_button 'Submit'
+      end
+
+      # key must be added. look for it in the refreshed page
+      assert page.has_text? 'added_in_test'
+    end
+  end
+
+  # Check manage profile page and add missing profile to the user
+  def add_profile user
+    assert page.has_no_text? 'My projects'
+    assert page.has_no_text? 'Projects shared with me'
+
+    assert page.has_text? 'Profile'
+    assert page.has_text? 'First name'
+    assert page.has_text? 'Last name'
+    assert page.has_text? 'Identity URL'
+    assert page.has_text? 'Email'
+    assert page.has_text? user['email']
+
+    # Using the default profile which has message and one required field
+
+    # Save profile without filling in the required field. Expect to be back in this profile page again
+    click_button "Save profile"
+    assert page.has_text? 'Profile'
+    assert page.has_text? 'First name'
+    assert page.has_text? 'Last name'
+    assert page.has_text? 'Save profile'
+
+    # This time fill in required field and then save. Expect to go to requested page after that.
+    profile_message = Rails.configuration.user_profile_form_message
+    required_field_title = ''
+    required_field_key = ''
+    profile_config = Rails.configuration.user_profile_form_fields
+    profile_config.andand.each do |entry|
+      if entry['required']
+        required_field_key = entry['key']
+        required_field_title = entry['form_field_title']
+      end
+    end
+
+    assert page.has_text? profile_message[0,25]
+    assert page.has_text? required_field_title
+    page.find_field('user[prefs][:profile][:'+required_field_key+']').set 'value to fill required field'
+
+    click_button "Save profile"
+    # profile saved and in profile page now with success
+    assert page.has_text? 'Thank you for filling in your profile'
+    click_button 'Access Arvados Workbench'
+
+    # profile saved and in home page now
+    assert page.has_text? 'My projects'
+    assert page.has_text? 'Projects shared with me'
+  end
+
+  # test the search box
+  def verify_search_box user
+    if user && user['is_active']
+      # let's search for a valid uuid
+      within('.navbar-fixed-top') do
+        page.find_field('search').set user['uuid']
+        page.find('.glyphicon-search').click
+      end
+
+      # we should now be in the user's page as a result of search
+      assert page.has_text? user['first_name']
+
+      # let's search again for an invalid valid uuid
+      within('.navbar-fixed-top') do
+        search_for = String.new user['uuid']
+        search_for[0]='1'
+        page.find_field('search').set search_for
+        page.find('.glyphicon-search').click
+      end
+
+      # we should see 'not found' error page
+      assert page.has_text? 'Not Found'
+
+      # let's search for the anonymously accessible project
+      publicly_accessible_project = api_fixture('groups')['anonymously_accessible_project']
+
+      within('.navbar-fixed-top') do
+        # search again for the anonymously accessible project
+        page.find_field('search').set publicly_accessible_project['name'][0,10]
+        page.find('.glyphicon-search').click
+      end
+
+      within '.modal-content' do
+        assert page.has_text? 'All projects'
+        assert page.has_text? 'Search'
+        assert page.has_text? 'Cancel'
+        assert_selector('div', text: publicly_accessible_project['name'])
+        find(:xpath, '//div[./span[contains(.,publicly_accessible_project["uuid"])]]').click
+
+        click_button 'Show'
+      end
+
+      # seeing "Unrestricted public data" now
+      assert page.has_text? publicly_accessible_project['name']
+      assert page.has_text? publicly_accessible_project['description']
+    end
+  end
+
+  [
+    [nil, nil, false, false],
+    ['inactive', api_fixture('users')['inactive'], true, false],
+    ['inactive_uninvited', api_fixture('users')['inactive_uninvited'], false, false],
+    ['active', api_fixture('users')['active'], true, true],
+    ['admin', api_fixture('users')['admin'], true, true],
+    ['active_no_prefs', api_fixture('users')['active_no_prefs'], true, false],
+    ['active_no_prefs_profile', api_fixture('users')['active_no_prefs_profile'], true, false],
+  ].each do |token, user, invited, has_profile|
+    test "visit home page when profile is configured for user #{token}" do
+      # Our test config enabled profile by default. So, no need to update config
+      if !token
+        visit ('/')
+      else
+        visit page_with_token(token)
+      end
+
+      verify_homepage_with_profile user, invited, has_profile
+    end
+
+    test "visit home page when profile not configured for user #{token}" do
+      Rails.configuration.user_profile_form_fields = false
+
+      if !token
+        visit ('/')
+      else
+        visit page_with_token(token)
+      end
+
+      verify_homepage_with_profile user, invited, has_profile
+    end
+
+    test "check help for user #{token}" do
+      if !token
+        visit ('/')
+      else
+        visit page_with_token(token)
+      end
+
+      check_help_menu
+    end
+  end
+
+  [
+    ['active', api_fixture('users')['active'], true, true],
+    ['admin', api_fixture('users')['admin'], true, true],
+  ].each do |token, user|
+    test "test system menu for user #{token}" do
+      visit page_with_token(token)
+      verify_system_menu user
+    end
+
+    test "test manage account for user #{token}" do
+      visit page_with_token(token)
+      verify_manage_account user
+    end
+
+    test "test search for user #{token}" do
+      visit page_with_token(token)
+      verify_search_box user
+    end
+  end
+end
index f5d6e633f340ea85cb7e438c35b208e7c3a13510..6385393b24f2c82cbb068e85aea4918d9844d379 100644 (file)
@@ -44,6 +44,9 @@ class PipelineInstancesTest < ActionDispatch::IntegrationTest
     within('.modal-dialog') do
       find('.btn', text: 'Add').click
     end
+    using_wait_time(Capybara.default_wait_time * 3) do
+      wait_for_ajax
+    end
 
     click_link 'Jobs and pipelines'
     find('tr[data-kind="arvados#pipelineInstance"]', text: 'New pipeline instance').
@@ -57,6 +60,8 @@ class PipelineInstancesTest < ActionDispatch::IntegrationTest
       click
 
     within('.modal-dialog') do
+      assert(has_text?("Foo/bar pair"),
+             "pipeline input picker missing name of input")
       first('span', text: 'foo_tag').click
       find('button', text: 'OK').click
     end
@@ -114,6 +119,7 @@ class PipelineInstancesTest < ActionDispatch::IntegrationTest
       click
 
     within('.modal-dialog') do
+      assert_selector 'button.dropdown-toggle', text: 'A Project'
       first('span', text: 'foo_tag').click
       find('button', text: 'OK').click
     end
diff --git a/apps/workbench/test/integration/pipeline_templates_test.rb b/apps/workbench/test/integration/pipeline_templates_test.rb
new file mode 100644 (file)
index 0000000..d131986
--- /dev/null
@@ -0,0 +1,15 @@
+require 'integration_helper'
+
+class PipelineTemplatesTest < ActionDispatch::IntegrationTest
+  test "JSON popup available for strange components" do
+    Capybara.current_driver = Capybara.javascript_driver
+    uuid = api_fixture("pipeline_templates")["components_is_jobspec"]["uuid"]
+    visit page_with_token("active", "/pipeline_templates/#{uuid}")
+    click_on "Components"
+    assert(page.has_no_text?("script_parameters"),
+           "components JSON visible without popup")
+    click_on "Show components JSON"
+    assert(page.has_text?("script_parameters"),
+           "components JSON not found")
+  end
+end
index adac0bf999c563ec46cc64016df5226c95da6083..b2c9d772d14b652688273023eeeb0d7ee9111017 100644 (file)
@@ -30,18 +30,20 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     specimen_uuid = api_fixture('specimens')['owned_by_aproject_with_no_name_link']['uuid']
     visit page_with_token 'active', '/projects/' + project_uuid
     click_link 'Other objects'
-    within(".selection-action-container") do
-      within (first('tr', text: 'Specimen')) do
+    within '.selection-action-container' do
+      # Wait for the tab to load:
+      assert_selector 'tr[data-kind="arvados#specimen"]'
+      within first('tr', text: 'Specimen') do
         find(".fa-pencil").click
         find('.editable-input input').set('Now I have a name.')
         find('.glyphicon-ok').click
-        find('.editable', text: 'Now I have a name.').click
+        assert_selector '.editable', text: 'Now I have a name.'
         find(".fa-pencil").click
         find('.editable-input input').set('Now I have a new name.')
         find('.glyphicon-ok').click
-        end
+      end
       wait_for_ajax
-      find('.editable', text: 'Now I have a new name.')
+      assert_selector '.editable', text: 'Now I have a new name.'
     end
     visit current_path
     click_link 'Other objects'
@@ -97,8 +99,20 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     assert(page.has_no_text?(name), "project is already shared with #{name}")
     start_share_count = share_rows.size
     click_on("Share with #{share_type}")
-    find(".selectable", text: name).click
-    find(".modal-footer a,button", text: "Add").click
+    within(".modal-container") do
+      # Order is important here: we should find something that appears in the
+      # modal before we make any assertions about what's not in the modal.
+      # Otherwise, the not-included assertions might falsely pass because
+      # the modal hasn't loaded yet.
+      find(".selectable", text: name).click
+      assert(has_no_selector?(".modal-dialog-preview-pane"),
+             "preview pane available in sharing dialog")
+      assert_raises(Capybara::ElementNotFound,
+                    "Projects pulldown available from sharing dialog") do
+        click_on "All projects"
+      end
+      click_on "Add"
+    end
     using_wait_time(Capybara.default_wait_time * 3) do
       assert(page.has_link?(name),
              "new share was not added to sharing table")
@@ -151,4 +165,16 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     add_share_and_check("groups", new_name)
     modify_share_and_check(new_name)
   end
+
+  test "'share with group' listing does not offer projects" do
+    show_project_using("active")
+    click_on "Sharing"
+    click_on "Share with groups"
+    good_uuid = api_fixture("groups")["private"]["uuid"]
+    assert(page.has_selector?(".selectable[data-object-uuid=\"#{good_uuid}\"]"),
+           "'share with groups' listing missing owned user group")
+    bad_uuid = api_fixture("groups")["asubproject"]["uuid"]
+    assert(page.has_no_selector?(".selectable[data-object-uuid=\"#{bad_uuid}\"]"),
+           "'share with groups' listing includes project")
+  end
 end
index efaea00b01c42ff4a06734e07be8505cf28125f4..1e3337044738cf3bca873d1545c96c850f636df0 100644 (file)
@@ -16,6 +16,9 @@ class SmokeTest < ActionDispatch::IntegrationTest
     all(find_spec + ' a').collect { |tag|
       if tag[:href].nil? or tag[:href].empty? or (tag.text !~ text_regexp)
         nil
+      elsif tag[:'data-remote']
+        # these don't necessarily work with format=html
+        nil
       else
         url = URI(tag[:href])
         url.host.nil? ? url.path : nil
index e833f970c02d211905cd504a4d7a25a13a25a016..260bff043c8cfc10cfc08f3d272835c0be214d51 100644 (file)
@@ -70,6 +70,9 @@ class ActiveSupport::TestCase
       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
     }
   end
+  def json_response
+    Oj.load(@response.body)
+  end
 end
 
 class ApiServerForTests
index df66cf66e0e9ae7ead91c1622e2411e0fc4c2b5b..c2962483999b04cad29d7e20ef06e508240cdb36 100755 (executable)
@@ -2,7 +2,7 @@
 
 import arvados
 import subprocess
-import subst
+import crunchutil.subst as subst
 import shutil
 import os
 import sys
index f16d62466a1e1853b56266ed58ef9935fd3f974b..ca80a82e3f26e7fc87a4373dbb7e7a529e37802e 100755 (executable)
@@ -1,8 +1,21 @@
 #!/usr/bin/env python
 
+# collection-merge
+#
+# Merge two or more collections together.  Can also be used to extract specific
+# files from a collection to produce a new collection.
+#
+# input:
+# An array of collections or collection/file paths in script_parameter["input"]
+#
+# output:
+# A manifest with the collections merged.  Duplicate file names will
+# have their contents concatenated in the order that they appear in the input
+# array.
+
 import arvados
 import md5
-import subst
+import crunchutil.subst as subst
 import subprocess
 import os
 import hashlib
@@ -30,28 +43,4 @@ for c in p["input"]:
                 if fn in s.files():
                     merged += s.files()[fn].as_manifest()
 
-crm = arvados.CollectionReader(merged)
-
-combined = crm.manifest_text(strip=True)
-
-m = hashlib.new('md5')
-m.update(combined)
-
-uuid = "{}+{}".format(m.hexdigest(), len(combined))
-
-collection = arvados.api().collections().create(
-    body={
-        'uuid': uuid,
-        'manifest_text': crm.manifest_text(),
-    }).execute()
-
-for s in src:
-    l = arvados.api().links().create(body={
-        "link": {
-            "tail_uuid": s,
-            "head_uuid": uuid,
-            "link_class": "provenance",
-            "name": "provided"
-        }}).execute()
-
-arvados.current_task().set_output(uuid)
+arvados.current_task().set_output(merged)
diff --git a/crunch_scripts/crunchutil/__init__.py b/crunch_scripts/crunchutil/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/crunch_scripts/crunchutil/robust_put.py b/crunch_scripts/crunchutil/robust_put.py
new file mode 100644 (file)
index 0000000..158ceb1
--- /dev/null
@@ -0,0 +1,48 @@
+import arvados
+import arvados.commands.put as put
+import os
+import logging
+
+def machine_progress(bytes_written, bytes_expected):
+    return "upload wrote {} total {}\n".format(
+        bytes_written, -1 if (bytes_expected is None) else bytes_expected)
+
+class Args(object):
+    def __init__(self, fn):
+        self.filename = None
+        self.paths = [fn]
+        self.max_manifest_depth = 0
+
+# Upload to Keep with error recovery.
+# Return a uuid or raise an exception if there are too many failures.
+def upload(source_dir):
+    source_dir = os.path.abspath(source_dir)
+    done = False
+    if 'TASK_WORK' in os.environ:
+        resume_cache = put.ResumeCache(os.path.join(arvados.current_task().tmpdir, "upload-output-checkpoint"))
+    else:
+        resume_cache = put.ResumeCache(put.ResumeCache.make_path(Args(source_dir)))
+    reporter = put.progress_writer(machine_progress)
+    bytes_expected = put.expected_bytes_for([source_dir])
+    backoff = 1
+    outuuid = None
+    while not done:
+        try:
+            out = put.ArvPutCollectionWriter.from_cache(resume_cache, reporter, bytes_expected)
+            out.do_queued_work()
+            out.write_directory_tree(source_dir, max_manifest_depth=0)
+            outuuid = out.finish()
+            done = True
+        except KeyboardInterrupt as e:
+            logging.critical("caught interrupt signal 2")
+            raise e
+        except Exception as e:
+            logging.exception("caught exception:")
+            backoff *= 2
+            if backoff > 256:
+                logging.critical("Too many upload failures, giving up")
+                raise e
+            else:
+                logging.warning("Sleeping for %s seconds before trying again" % backoff)
+                time.sleep(backoff)
+    return outuuid
similarity index 91%
rename from crunch_scripts/subst.py
rename to crunch_scripts/crunchutil/subst.py
index 2598e1cc944397324c9bb02b931a8bc7c20ed5a8..b3526883fcf193ff719c249a37343afe45e340e7 100644 (file)
@@ -44,7 +44,11 @@ def sub_basename(v):
     return os.path.splitext(os.path.basename(v))[0]
 
 def sub_glob(v):
-    return glob.glob(v)[0]
+    l = glob.glob(v)
+    if len(l) == 0:
+        raise Exception("$(glob): No match on '%s'" % v)
+    else:
+        return l[0]
 
 default_subs = {"file ": sub_file,
                 "dir ": sub_dir,
@@ -55,7 +59,7 @@ def do_substitution(p, c, subs=default_subs):
     while True:
         #print("c is", c)
         m = search(c)
-        if m != None:
+        if m is not None:
             v = do_substitution(p, c[m[0]+2 : m[1]])
             var = True
             for sub in subs:
diff --git a/crunch_scripts/crunchutil/vwd.py b/crunch_scripts/crunchutil/vwd.py
new file mode 100644 (file)
index 0000000..3d54c9c
--- /dev/null
@@ -0,0 +1,54 @@
+import arvados
+import os
+import robust_put
+import stat
+
+# Implements "Virtual Working Directory"
+# Provides a way of emulating a shared writable directory in Keep based
+# on a "check out, edit, check in, merge" model.
+# At the moment, this only permits adding new files, applications
+# cannot modify or delete existing files.
+
+# Create a symlink tree rooted at target_dir mirroring arv-mounted
+# source_collection.  target_dir must be empty, and will be created if it
+# doesn't exist.
+def checkout(source_collection, target_dir, keepmount=None):
+    # create symlinks
+    if keepmount is None:
+        keepmount = os.environ['TASK_KEEPMOUNT']
+
+    if not os.path.exists(target_dir):
+        os.makedirs(target_dir)
+
+    l = os.listdir(target_dir)
+    if len(l) > 0:
+        raise Exception("target_dir must be empty before checkout, contains %s" % l)
+
+    stem = os.path.join(keepmount, source_collection)
+    for root, dirs, files in os.walk(os.path.join(keepmount, source_collection), topdown=True):
+        rel = root[len(stem)+1:]
+        for d in dirs:
+            os.mkdir(os.path.join(target_dir, rel, d))
+        for f in files:
+            os.symlink(os.path.join(root, f), os.path.join(target_dir, rel, f))
+
+# Delete all symlinks and check in any remaining normal files.
+# If merge == True, merge the manifest with source_collection and return a
+# CollectionReader for the combined collection.
+def checkin(source_collection, target_dir, merge=True):
+    # delete symlinks, commit directory, merge manifests and return combined
+    # collection.
+    for root, dirs, files in os.walk(target_dir):
+        for f in files:
+            s = os.lstat(os.path.join(root, f))
+            if stat.S_ISLNK(s.st_mode):
+                os.unlink(os.path.join(root, f))
+
+    uuid = robust_put.upload(target_dir)
+    if merge:
+        cr1 = arvados.CollectionReader(source_collection)
+        cr2 = arvados.CollectionReader(uuid)
+        combined = arvados.CollectionReader(cr1.manifest_text() + cr2.manifest_text())
+        return combined
+    else:
+        return arvados.CollectionReader(uuid)
diff --git a/crunch_scripts/decompress-all.py b/crunch_scripts/decompress-all.py
new file mode 100755 (executable)
index 0000000..50d11f4
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+#
+# decompress-all.py
+#
+# Decompress all compressed files in the collection using the "dtrx" tool and
+# produce a new collection with the contents.  Uncompressed files
+# are passed through.
+#
+# input:
+# A collection at script_parameters["input"]
+#
+# output:
+# A manifest of the uncompressed contents of the input collection.
+
+import arvados
+import re
+import subprocess
+import os
+import sys
+import crunchutil.robust_put as robust_put
+
+arvados.job_setup.one_task_per_input_file(if_sequence=0, and_end_task=True,
+                                          input_as_path=True)
+
+task = arvados.current_task()
+
+input_file = task['parameters']['input']
+
+infile_parts = re.match(r"(^[a-f0-9]{32}\+\d+)(\+\S+)*(/.*)?(/[^/]+)$", input_file)
+
+outdir = os.path.join(task.tmpdir, "output")
+os.makedirs(outdir)
+os.chdir(outdir)
+
+if infile_parts is None:
+    print >>sys.stderr, "Failed to parse input filename '%s' as a Keep file\n" % input_file
+    sys.exit(1)
+
+cr = arvados.CollectionReader(infile_parts.group(1))
+streamname = infile_parts.group(3)[1:]
+filename = infile_parts.group(4)[1:]
+
+if streamname is not None:
+    subprocess.call(["mkdir", "-p", streamname])
+    os.chdir(streamname)
+else:
+    streamname = '.'
+
+m = re.match(r'.*\.(gz|Z|bz2|tgz|tbz|zip|rar|7z|cab|deb|rpm|cpio|gem)$', arvados.get_task_param_mount('input'), re.IGNORECASE)
+
+if m is not None:
+    rc = subprocess.call(["dtrx", "-r", "-n", "-q", arvados.get_task_param_mount('input')])
+    if rc == 0:
+        task.set_output(robust_put.upload(outdir))
+    else:
+        sys.exit(rc)
+else:
+    streamreader = filter(lambda s: s.name() == streamname, cr.all_streams())[0]
+    filereader = streamreader.files()[filename]
+    task.set_output(streamname + filereader.as_manifest()[1:])
index e6ec889bbcbdbd731b7e6930379b1957062b4178..c624e3cadf7ec7fdaad169f04ffb8f34f8bcdd9f 100755 (executable)
@@ -1,15 +1,25 @@
 #!/usr/bin/env python
 
+import logging
+logging.basicConfig(level=logging.INFO, format="run-command: %(message)s")
+
 import arvados
 import re
 import os
 import subprocess
 import sys
 import shutil
-import subst
+import crunchutil.subst as subst
 import time
 import arvados.commands.put as put
 import signal
+import stat
+import copy
+import traceback
+import pprint
+import multiprocessing
+import crunchutil.robust_put as robust_put
+import crunchutil.vwd as vwd
 
 os.umask(0077)
 
@@ -23,34 +33,39 @@ os.mkdir("output")
 
 os.chdir("output")
 
+outdir = os.getcwd()
+
+taskp = None
+jobp = arvados.current_job()['script_parameters']
 if len(arvados.current_task()['parameters']) > 0:
-    p = arvados.current_task()['parameters']
-else:
-    p = arvados.current_job()['script_parameters']
+    taskp = arvados.current_task()['parameters']
 
 links = []
 
-def sub_link(v):
-    r = os.path.basename(v)
-    os.symlink(os.path.join(os.environ['TASK_KEEPMOUNT'], v) , r)
-    links.append(r)
-    return r
-
 def sub_tmpdir(v):
     return os.path.join(arvados.current_task().tmpdir, 'tmpdir')
 
+def sub_outdir(v):
+    return outdir
+
 def sub_cores(v):
-     return os.environ['CRUNCH_NODE_SLOTS']
+     return str(multiprocessing.cpu_count())
 
-subst.default_subs["link "] = sub_link
-subst.default_subs["tmpdir"] = sub_tmpdir
-subst.default_subs["node.cores"] = sub_cores
+def sub_jobid(v):
+     return os.environ['JOB_UUID']
 
-rcode = 1
+def sub_taskid(v):
+     return os.environ['TASK_UUID']
 
-def machine_progress(bytes_written, bytes_expected):
-    return "run-command: wrote {} total {}\n".format(
-        bytes_written, -1 if (bytes_expected is None) else bytes_expected)
+def sub_jobsrc(v):
+     return os.environ['CRUNCH_SRC']
+
+subst.default_subs["task.tmpdir"] = sub_tmpdir
+subst.default_subs["task.outdir"] = sub_outdir
+subst.default_subs["job.srcdir"] = sub_jobsrc
+subst.default_subs["node.cores"] = sub_cores
+subst.default_subs["job.uuid"] = sub_jobid
+subst.default_subs["task.uuid"] = sub_taskid
 
 class SigHandler(object):
     def __init__(self):
@@ -60,20 +75,106 @@ class SigHandler(object):
         sp.send_signal(signum)
         self.sig = signum
 
+def expand_item(p, c):
+    if isinstance(c, dict):
+        if "foreach" in c and "command" in c:
+            var = c["foreach"]
+            items = get_items(p, p[var])
+            r = []
+            for i in items:
+                params = copy.copy(p)
+                params[var] = i
+                r.extend(expand_list(params, c["command"]))
+            return r
+    elif isinstance(c, list):
+        return expand_list(p, c)
+    elif isinstance(c, str) or isinstance(c, unicode):
+        return [subst.do_substitution(p, c)]
+
+    return []
+
+def expand_list(p, l):
+    return [exp for arg in l for exp in expand_item(p, arg)]
+
+def get_items(p, value):
+    if isinstance(value, list):
+        return expand_list(p, value)
+
+    fn = subst.do_substitution(p, value)
+    mode = os.stat(fn).st_mode
+    prefix = fn[len(os.environ['TASK_KEEPMOUNT'])+1:]
+    if mode is not None:
+        if stat.S_ISDIR(mode):
+            items = ["$(dir %s/%s/)" % (prefix, l) for l in os.listdir(fn)]
+        elif stat.S_ISREG(mode):
+            with open(fn) as f:
+                items = [line for line in f]
+        return items
+    else:
+        return None
+
+stdoutname = None
+stdoutfile = None
+stdinname = None
+stdinfile = None
+rcode = 1
+
 try:
-    cmd = []
-    for c in p["command"]:
-        cmd.append(subst.do_substitution(p, c))
-
-    stdoutname = None
-    stdoutfile = None
-    if "stdout" in p:
-        stdoutname = subst.do_substitution(p, p["stdout"])
+    if "task.foreach" in jobp:
+        if arvados.current_task()['sequence'] == 0:
+            var = jobp["task.foreach"]
+            items = get_items(jobp, jobp[var])
+            logging.info("parallelizing on %s with items %s" % (var, items))
+            if items is not None:
+                for i in items:
+                    params = copy.copy(jobp)
+                    params[var] = i
+                    arvados.api().job_tasks().create(body={
+                        'job_uuid': arvados.current_job()['uuid'],
+                        'created_by_job_task_uuid': arvados.current_task()['uuid'],
+                        'sequence': 1,
+                        'parameters': params
+                        }
+                    ).execute()
+                if "task.vwd" in jobp:
+                    # Base vwd collection will be merged with output fragments from
+                    # the other tasks by crunch.
+                    arvados.current_task().set_output(subst.do_substitution(jobp, jobp["task.vwd"]))
+                else:
+                    arvados.current_task().set_output(None)
+                sys.exit(0)
+            else:
+                sys.exit(1)
+    else:
+        taskp = jobp
+
+    if "task.vwd" in taskp:
+        # Populate output directory with symlinks to files in collection
+        vwd.checkout(subst.do_substitution(taskp, taskp["task.vwd"]), outdir)
+
+    if "task.cwd" in taskp:
+        os.chdir(subst.do_substitution(taskp, taskp["task.cwd"]))
+
+    cmd = expand_list(taskp, taskp["command"])
+
+    if "task.stdin" in taskp:
+        stdinname = subst.do_substitution(taskp, taskp["task.stdin"])
+        stdinfile = open(stdinname, "rb")
+
+    if "task.stdout" in taskp:
+        stdoutname = subst.do_substitution(taskp, taskp["task.stdout"])
         stdoutfile = open(stdoutname, "wb")
 
-    print("run-command: {}{}".format(' '.join(cmd), (" > " + stdoutname) if stdoutname != None else ""))
+    logging.info("{}{}{}".format(' '.join(cmd), (" < " + stdinname) if stdinname is not None else "", (" > " + stdoutname) if stdoutname is not None else ""))
+
+except Exception as e:
+    logging.exception("caught exception")
+    logging.error("task parameters was:")
+    logging.error(pprint.pformat(taskp))
+    sys.exit(1)
 
-    sp = subprocess.Popen(cmd, shell=False, stdout=stdoutfile)
+try:
+    sp = subprocess.Popen(cmd, shell=False, stdin=stdinfile, stdout=stdoutfile)
     sig = SigHandler()
 
     # forward signals to the process.
@@ -84,14 +185,14 @@ try:
     # wait for process to complete.
     rcode = sp.wait()
 
-    if sig.sig != None:
-        print("run-command: terminating on signal %s" % sig.sig)
+    if sig.sig is not None:
+        logging.critical("terminating on signal %s" % sig.sig)
         sys.exit(2)
     else:
-        print("run-command: completed with exit code %i (%s)" % (rcode, "success" if rcode == 0 else "failed"))
+        logging.info("completed with exit code %i (%s)" % (rcode, "success" if rcode == 0 else "failed"))
 
 except Exception as e:
-    print("run-command: caught exception: {}".format(e))
+    logging.exception("caught exception")
 
 # restore default signal handlers.
 signal.signal(signal.SIGINT, signal.SIG_DFL)
@@ -101,34 +202,27 @@ signal.signal(signal.SIGQUIT, signal.SIG_DFL)
 for l in links:
     os.unlink(l)
 
-print("run-command: the follow output files will be saved to keep:")
-
-subprocess.call(["find", ".", "-type", "f", "-printf", "run-command: %12.12s %h/%f\\n"])
-
-print("run-command: start writing output to keep")
-
-done = False
-resume_cache = put.ResumeCache(os.path.join(arvados.current_task().tmpdir, "upload-output-checkpoint"))
-reporter = put.progress_writer(machine_progress)
-bytes_expected = put.expected_bytes_for(".")
-while not done:
-    try:
-        out = put.ArvPutCollectionWriter.from_cache(resume_cache, reporter, bytes_expected)
-        out.do_queued_work()
-        out.write_directory_tree(".", max_manifest_depth=0)
-        outuuid = out.finish()
-        api.job_tasks().update(uuid=arvados.current_task()['uuid'],
-                                             body={
-                                                 'output':outuuid,
-                                                 'success': (rcode == 0),
-                                                 'progress':1.0
-                                             }).execute()
-        done = True
-    except KeyboardInterrupt:
-        print("run-command: terminating on signal 2")
-        sys.exit(2)
-    except Exception as e:
-        print("run-command: caught exception: {}".format(e))
-        time.sleep(5)
+logging.info("the following output files will be saved to keep:")
+
+subprocess.call(["find", ".", "-type", "f", "-printf", "run-command: %12.12s %h/%f\\n"], stdout=sys.stderr)
+
+logging.info("start writing output to keep")
+
+if "task.vwd" in taskp:
+    if "task.foreach" in jobp:
+        # This is a subtask, so don't merge with the original collection, that will happen at the end
+        outcollection = vwd.checkin(subst.do_substitution(taskp, taskp["task.vwd"]), outdir, merge=False).manifest_text()
+    else:
+        # Just a single task, so do merge with the original collection
+        outcollection = vwd.checkin(subst.do_substitution(taskp, taskp["task.vwd"]), outdir, merge=True).manifest_text()
+else:
+    outcollection = robust_put.upload(outdir)
+
+api.job_tasks().update(uuid=arvados.current_task()['uuid'],
+                                     body={
+                                         'output': outcollection,
+                                         'success': (rcode == 0),
+                                         'progress':1.0
+                                     }).execute()
 
 sys.exit(rcode)
diff --git a/crunch_scripts/split-fastq.py b/crunch_scripts/split-fastq.py
new file mode 100755 (executable)
index 0000000..253dd22
--- /dev/null
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+
+import arvados
+import re
+import hashlib
+import string
+
+api = arvados.api('v1')
+
+piece = 0
+manifest_text = ""
+
+# Look for paired reads
+
+inp = arvados.CollectionReader(arvados.getjobparam('reads'))
+
+manifest_list = []
+
+chunking = False #arvados.getjobparam('chunking')
+
+def nextline(reader, start):
+    n = -1
+    while True:
+        r = reader.readfrom(start, 128)
+        if r == '':
+            break
+        n = string.find(r, "\n")
+        if n > -1:
+            break
+        else:
+            start += 128
+    return n
+
+# Chunk a fastq into approximately 64 MiB chunks.  Requires that the input data
+# be decompressed ahead of time, such as using decompress-all.py.  Generates a
+# new manifest, but doesn't actually move any data around.  Handles paired
+# reads by ensuring that each chunk of a pair gets the same number of records.
+#
+# This works, but in practice is so slow that potential gains in alignment
+# performance are lost in the prep time, which is why it is currently disabled.
+#
+# A better algorithm would seek to a file position a bit less than the desired
+# chunk size and then scan ahead for the next record, making sure that record
+# was matched by the read pair.
+def splitfastq(p):
+    for i in xrange(0, len(p)):
+        p[i]["start"] = 0
+        p[i]["end"] = 0
+
+    count = 0
+    recordsize = [0, 0]
+
+    global piece
+    finish = False
+    while not finish:
+        for i in xrange(0, len(p)):
+            recordsize[i] = 0
+
+        # read next 4 lines
+        for i in xrange(0, len(p)):
+            for ln in xrange(0, 4):
+                r = nextline(p[i]["reader"], p[i]["end"]+recordsize[i])
+                if r == -1:
+                    finish = True
+                    break
+                recordsize[i] += (r+1)
+
+        splitnow = finish
+        for i in xrange(0, len(p)):
+            if ((p[i]["end"] - p[i]["start"]) + recordsize[i]) >= (64*1024*1024):
+                splitnow = True
+
+        if splitnow:
+            for i in xrange(0, len(p)):
+                global manifest_list
+                print >>sys.stderr, "Finish piece ./_%s/%s (%s %s)" % (piece, p[i]["reader"].name(), p[i]["start"], p[i]["end"])
+                manifest = []
+                manifest.extend(["./_" + str(piece)])
+                manifest.extend([d[arvados.LOCATOR] for d in p[i]["reader"]._stream._data_locators])
+                manifest.extend(["{}:{}:{}".format(seg[arvados.LOCATOR]+seg[arvados.OFFSET], seg[arvados.SEGMENTSIZE], p[i]["reader"].name().replace(' ', '\\040')) for seg in arvados.locators_and_ranges(p[i]["reader"].segments, p[i]["start"], p[i]["end"] - p[i]["start"])])
+                manifest_list.append(manifest)
+                p[i]["start"] = p[i]["end"]
+            piece += 1
+        else:
+            for i in xrange(0, len(p)):
+                p[i]["end"] += recordsize[i]
+            count += 1
+            if count % 10000 == 0:
+                print >>sys.stderr, "Record %s at %s" % (count, p[i]["end"])
+
+prog = re.compile(r'(.*?)(_[12])?\.fastq(\.gz)?$')
+
+# Look for fastq files
+for s in inp.all_streams():
+    for f in s.all_files():
+        name_pieces = prog.match(f.name())
+        if name_pieces is not None:
+            if s.name() != ".":
+                # The downstream tool (run-command) only iterates over the top
+                # level of directories so if there are fastq files in
+                # directories in the input, the choice is either to forget
+                # there are directories (which might lead to name conflicts) or
+                # just fail.
+                print >>sys.stderr, "fastq must be at the root of the collection"
+                sys.exit(1)
+
+            p = None
+            if name_pieces.group(2) is not None:
+                if name_pieces.group(2) == "_1":
+                    p = [{}, {}]
+                    p[0]["reader"] = s.files()[name_pieces.group(0)]
+                    p[1]["reader"] = s.files()[name_pieces.group(1) + "_2.fastq" + (name_pieces.group(3) if name_pieces.group(3) else '')]
+            else:
+                p = [{}]
+                p[0]["reader"] = s.files()[name_pieces.group(0)]
+
+            if p is not None:
+                if chunking:
+                    splitfastq(p)
+                else:
+                    for i in xrange(0, len(p)):
+                        m = p[i]["reader"].as_manifest()[1:]
+                        manifest_list.append(["./_" + str(piece), m[:-1]])
+                    piece += 1
+
+manifest_text = "\n".join(" ".join(m) for m in manifest_list)
+
+arvados.current_task().set_output(manifest_text)
index a6fb3d65ae3d9c3a4c6c44e75fb50fad42f37b2c..3301207250710e9d706d4177c2f12caa3ec257f9 100644 (file)
@@ -4,7 +4,7 @@
 # workbench, use /doc here and add a symlink at
 # apps/workbench/public/doc pointing to ../../../doc/.site
 # You can also set these on the command line:
-# $ rake generate baseurl=/example arvados_api_host=example.comA
+# $ rake generate baseurl=/example arvados_api_host=example.com
 
 baseurl:
 arvados_api_host: localhost
@@ -16,31 +16,32 @@ navbar:
   userguide:
     - Getting Started:
       - user/index.html.textile.liquid
+      - user/getting_started/community.html.textile.liquid
+    - Run a pipeline using Workbench:
       - user/getting_started/workbench.html.textile.liquid
+      - user/tutorials/tutorial-pipeline-workbench.html.textile.liquid
+    - Access an Arvados virtual machine:
       - user/getting_started/ssh-access-unix.html.textile.liquid
       - user/getting_started/ssh-access-windows.html.textile.liquid
       - user/getting_started/check-environment.html.textile.liquid
-      - user/getting_started/community.html.textile.liquid
-    - Tutorials:
+      - user/reference/api-tokens.html.textile.liquid
+    - Working with data sets:
       - user/tutorials/tutorial-keep.html.textile.liquid
+      - user/tutorials/tutorial-keep-get.html.textile.liquid
+      - user/tutorials/tutorial-keep-mount.html.textile.liquid
+      - user/topics/keep.html.textile.liquid
+    - Run a pipeline on the command line:
+      - user/topics/running-pipeline-command-line.html.textile.liquid
+      - user/reference/sdk-cli.html.textile.liquid
+    - Develop a new pipeline:
       - user/tutorials/intro-crunch.html.textile.liquid
-      - user/tutorials/tutorial-pipeline-workbench.html.textile.liquid
-      - user/tutorials/tutorial-firstscript.html.textile.liquid
-      - user/tutorials/tutorial-new-pipeline.html.textile.liquid
       - user/tutorials/running-external-program.html.textile.liquid
-    - Intermediate topics:
-      - user/topics/running-pipeline-command-line.html.textile.liquid
-      - user/topics/tutorial-job1.html.textile.liquid
-      - user/topics/tutorial-job-debug.html.textile.liquid
+      - user/tutorials/tutorial-firstscript.html.textile.liquid
+      - user/tutorials/tutorial-submit-job.html.textile.liquid
       - user/topics/tutorial-parallel.html.textile.liquid
-      - user/topics/tutorial-trait-search.html.textile.liquid
-      - user/topics/tutorial-gatk-variantfiltration.html.textile.liquid
-      - user/topics/keep.html.textile.liquid
-    - Examples:
       - user/examples/crunch-examples.html.textile.liquid
-    - Reference:
-      - user/reference/api-tokens.html.textile.liquid
-      - user/reference/sdk-cli.html.textile.liquid
+    - Query the metadata database:
+      - user/topics/tutorial-trait-search.html.textile.liquid
     - Arvados License:
       - user/copying/copying.html.textile.liquid
       - user/copying/agpl-3.0.html
@@ -59,6 +60,8 @@ navbar:
       - sdk/ruby/index.html.textile.liquid
     - Java:
       - sdk/java/index.html.textile.liquid
+    - Go:
+      - sdk/go/index.html.textile.liquid
     - CLI:
       - sdk/cli/index.html.textile.liquid
   api:
@@ -121,6 +124,7 @@ navbar:
     - Docker:
       - install/install-docker.html.textile.liquid
     - Manual installation:
+      - install/install-keep.html.textile.liquid
       - install/install-sso.html.textile.liquid
       - install/install-api-server.html.textile.liquid
       - install/install-workbench-app.html.textile.liquid
diff --git a/doc/_includes/_alert_docker.liquid b/doc/_includes/_alert_docker.liquid
deleted file mode 100644 (file)
index be4a8e0..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="alert alert-block alert-info">
-  <button type="button" class="close" data-dismiss="alert">&times;</button>
-  <p>The Docker installation is not feature complete. We do not have a Docker container yet for crunch-dispatch and the arvados compute nodes. This means that running pipelines from a Docker-based Arvados install is currently not supported without additional manual configuration. Without that manual configuration, it is possible to use arv-crunch-job to run a 'local' job against your Docker-based Arvados installation. To do this, please refer to the "Debugging a Crunch script":{{site.baseurl}}/user/topics/tutorial-job-debug.html page.</p>
-</div>
index 8f2a23320f3f59f265a55d39d7a297ab5a743aad..412ae6740fec6625dde72e043258c2b98ea8443b 100644 (file)
@@ -9,18 +9,18 @@ If you have no SSH keys registered, there should be a notification asking you to
 
 h3. Alternate way to add SSH keys
 
-If you want to add more SSH keys, click on the link with your _email address_ in the upper right corner to access the user settings menu, and click on the menu item *Manage ssh keys* to go to the Authorized keys page.
+Click on the link with your _email address_ in the upper right corner to access the user settings menu, and click on the menu item *Manage account* to go to the account management page.
 
-On the *Authorized keys* page, the click on the button <span class="btn btn-primary disabled">Add a new authorized key</span> in the upper right corner.
+On the *Manage account* page, click on the button <span class="btn btn-primary">Add new SSH key</span> button in the upper right corner of the page in the SSH Keys panel.
 
-A new authorized key will be added and you will be taken to the key's page. Towards the bottom of this page you will see the *public_key* attribute. Click on the cell +New authorized key+.  This will open an editing popup as shown in this screenshot:
+This will open a popup as shown in this screenshot:
 
 !{{ site.baseurl }}/images/ssh-adding-public-key.png!
 
-Paste the public key that you copied to the cliboard in the previous section into the popup text box, then click on the check mark to save it.  This should refresh the page with the public key that you just added as the *public_key* attribute value.  You are now ready to "log into an Arvados VM":#login.
+Paste the public key that you copied to the cliboard in the previous section into the popup text box labeled *Public Key* and click on the <span class="btn btn-primary">Submit</span> button to save it. This should refresh the Manage account page with the fingerprint of the public key that you just added in the SSH Keys panel.  You are now ready to "log into an Arvados VM":#login.
 
 h1(#login). Using SSH to log into an Arvados VM
 
-To see a list of virtual machines that you have access to and determine the name and login information, click on *Settings* menu in the top navigation bar and click on *Virtual machines*.  Once on the *Virtual machines* page, The *hostname* column lists the name of each available VM.  The *logins* column will have a value in the form of @["you"]@.  Your login name is the text inside the quotes.  In this guide the hostname will be *_shell_* and the login will be *_you_*.  Replace these with your hostname and login name as appropriate.
+To see a list of virtual machines that you have access to and determine the name and login information, click on the link with your _email address_ in the upper right corner and click on the menu item *Manage account* to go to the account management page. On this page, you will see a *Virtual Machines* panel, which lists the virtual machines you can access. The *hostname* column lists the name of each available VM.  The *logins* column will have a list of comma separated values of the form @you@. In this guide the hostname will be *_shell_* and the login will be *_you_*.  Replace these with your hostname and login name as appropriate.
 
 
diff --git a/doc/_includes/_tutorial_bwa_sortsam_pipeline.liquid b/doc/_includes/_tutorial_bwa_sortsam_pipeline.liquid
new file mode 100644 (file)
index 0000000..c2be9e2
--- /dev/null
@@ -0,0 +1,65 @@
+{
+    "name": "Tutorial align using bwa mem and SortSam",
+    "components": {
+        "bwa-mem": {
+            "script": "run-command",
+            "script_version": "master",
+            "repository": "arvados",
+            "script_parameters": {
+                "command": [
+                    "bwa",
+                    "mem",
+                    "-t",
+                    "$(node.cores)",
+                    "-R",
+                    "@RG\\tID:group_id\\tPL:illumina\\tSM:sample_id",
+                    "$(glob $(dir $(reference_collection))/*.fasta)",
+                    "$(glob $(dir $(sample))/*_1.fastq)",
+                    "$(glob $(dir $(sample))/*_2.fastq)"
+                ],
+                "reference_collection": {
+                    "required": true,
+                    "dataclass": "Collection"
+                },
+                "sample": {
+                    "required": true,
+                    "dataclass": "Collection"
+                },
+                "stdout": "$(basename $(glob $(dir $(sample))/*_1.fastq)).sam"
+            },
+            "runtime_constraints": {
+                "docker_image": "arvados/jobs-java-bwa-samtools"
+            }
+        },
+        "SortSam": {
+            "script": "run-command",
+            "script_version": "847459b3c257aba65df3e0cbf6777f7148542af2",
+            "repository": "arvados",
+            "script_parameters": {
+                "command": [
+                    "java",
+                    "-Xmx4g",
+                    "-Djava.io.tmpdir=$(tmpdir)",
+                    "-jar",
+                    "$(dir $(picard))/SortSam.jar",
+                    "CREATE_INDEX=True",
+                    "SORT_ORDER=coordinate",
+                    "VALIDATION_STRINGENCY=LENIENT",
+                    "INPUT=$(glob $(dir $(input))/*.sam)",
+                    "OUTPUT=$(basename $(glob $(dir $(input))/*.sam)).sort.bam"
+                ],
+                "input": {
+                    "output_of": "bwa-mem"
+                },
+                "picard": {
+                    "required": true,
+                    "dataclass": "Collection",
+                    "default": "88447c464574ad7f79e551070043f9a9+1970"
+                }
+            },
+            "runtime_constraints": {
+                "docker_image": "arvados/jobs-java-bwa-samtools"
+            }
+        }
+    }
+}
index 86ac64c90d1bbd34682943bdae3d19d3597aa20c..6a8b5b51b70eb22ef72fa4967e4129134f461fbc 100644 (file)
@@ -1 +1 @@
-*This tutorial assumes that you are logged into an Arvados VM instance ("Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login), and have a "working environment.":{{site.baseurl}}/user/getting_started/check-environment.html*
+*This tutorial assumes either that you are logged into an Arvados VM instance (instructions for "Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login) or you have installed the Arvados "Command line SDK":{{site.baseurl}}/sdk/cli/index.html and "Python SDK":{{site.baseurl}}/sdk/python/sdk-python.html on your workstation and have a "working environment.":{{site.baseurl}}/user/getting_started/check-environment.html*
index 4585b7032abc7d91e8869ff4319b061ba9ad0d09..88da01b178669ee2065536e9e2e532f802ee1d35 100644 (file)
@@ -12,6 +12,7 @@
     <link href="{{ site.baseurl }}/css/nav-list.css" rel="stylesheet">
     <link href="{{ site.baseurl }}/css/badges.css" rel="stylesheet">
     <link href="{{ site.baseurl }}/css/code.css" rel="stylesheet">
+    <link href="{{ site.baseurl }}/css/font-awesome.css" rel="stylesheet">
     <style>
       html {
       height:100%;
@@ -58,7 +59,7 @@
       margin-top: -61px;
       }
     </style>
-    
+
     <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
     <!--[if lt IE 9]>
         <script src="../assets/js/html5shiv.js"></script>
 The content of this documentation is licensed under the
 <a href="{{ site.baseurl }}/user/copying/by-sa-3.0.html">Creative
   Commons Attribution-Share Alike 3.0 United States</a> licence.<br>
-Code samples in this documentation are licensed under the 
+Code samples in this documentation are licensed under the
 <a href="{{ site.baseurl }}/user/copying/LICENSE-2.0.html">Apache License, Version 2.0.</a></small>
 </p>
 
   </body>
 </html>
-
index f4eabcfaa8e8227b04e2d8390960cb01de0237cd..8760fe88edce9c41ab21e8518edcd66064b371bf 100644 (file)
@@ -53,6 +53,9 @@ table(table table-bordered table-condensed).
 |limit|integer (default 100)|Maximum number of collections to return.|query||
 |order|string|Order in which to return matching collections.|query||
 |filters|array|Conditions for filtering collections.|query||
+|select|array|Data fields to return in the result list.|query|@["uuid", "manifest_text"]@|
+
+N.B.: Because adding access tokens to manifests can be computationally expensive, the @manifest_text@ field is not included in results by default.  If you need it, pass a @select@ parameter that includes @manifest_text@.
 
 h2. update
 
index 4613326f564ca466f73f74e1ebaad358fc3b5f00..8a542650d2ea482ff3198a8ff3c13824c54dd332 100644 (file)
@@ -19,6 +19,6 @@ Each Group has, in addition to the usual "attributes of Arvados resources":{{sit
 table(table table-bordered table-condensed).
 |_. Attribute|_. Type|_. Description|_. Example|
 |name|string|||
-|group_class|string|Type of group. This does not affect behavior, but determines how the group is presented in the user interface. For example, @folder@ indicates that the group should be displayed by Workbench and arv-mount as a folder for organizing and naming objects.|@"folder"@
+|group_class|string|Type of group. This does not affect behavior, but determines how the group is presented in the user interface. For example, @project@ indicates that the group should be displayed by Workbench and arv-mount as a project for organizing and naming objects.|@"project"@
 null|
 |description|text|||
index 9e81ec05f3b537c07d6655e3b7513f7a0302b9af..412810f02eef85a1b63953a55e124381e0ea0ed0 100644 (file)
@@ -21,12 +21,18 @@ table(table table-bordered table-condensed).
 |default         |any     |any                                              |The default value for this parameter.|
 |required        |boolean |true or false                                    |Specifies whether the parameter is required to have a value or not.|
 |dataclass       |string  |One of 'Collection', 'File' [1], 'number', or 'text' |Data type of this parameter.|
+|search_for      |string  |any string                                       |Substring to use as a default search string when choosing inputs.|
 |output_of       |string  |the name of another component in the pipeline    |Specifies that the value of this parameter should be set to the 'output' attribute of the job that corresponds to the specified component.|
+|title           |string  |any string                                       |User friendly title to display when choosing parameter values|
+|description     |string  |any string                                       |Extended text description for describing expected/valid values for the script parameter|
+|link_name       |string  |any string                                       |User friendly name to display for the parameter value instead of the actual parameter value|
 
 The 'output_of' parameter is especially important, as this is how components are actually linked together to form a pipeline.  Component jobs that depend on the output of other components do not run until the parent job completes and has produced output.  If the parent job fails, the entire pipeline fails.
 
 fn1. The 'File' type refers to a specific file within a Keep collection in the form 'collection_hash/filename', for example '887cd41e9c613463eab2f0d885c6dd96+83/bob.txt'.
 
+The 'search_for' parameter is meaningful only when input dataclass of type Collection or File is used. If a value is provided, this will be preloaded into the input data chooser dialog in Workbench. For example, if your input dataclass is a File and you are interested in a certain filename extention, you can preconfigure it in this attribute.
+
 h3. Examples
 
 This is a pipeline named "Filter MD5 hash values" with two components, "do_hash" and "filter".  The "input" script parameter of the "do_hash" component is required to be filled in by the user, and the expected data type is "Collection".  This also specifies that the "input" script parameter of the "filter" component is the output of "do_hash", so "filter" will not run until "do_hash" completes successfully.  When the pipeline runs, past jobs that meet the criteria described above may be substituted for either or both components to avoid redundant computation.
@@ -42,7 +48,9 @@ This is a pipeline named "Filter MD5 hash values" with two components, "do_hash"
       "script_parameters": {
         "input": {
           "required": true,
-          "dataclass": "Collection"
+          "dataclass": "Collection",
+          "search_for": ".fastq.gz",
+          "title":"Please select a fastq file"
         }
       },
     },
diff --git a/doc/css/font-awesome.css b/doc/css/font-awesome.css
new file mode 100644 (file)
index 0000000..eb4127b
--- /dev/null
@@ -0,0 +1,1566 @@
+/*!
+ *  Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../fonts/fontawesome-webfont.eot?v=4.1.0');
+  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+.fa {
+  display: inline-block;
+  font-family: FontAwesome;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+  font-size: 1.33333333em;
+  line-height: 0.75em;
+  vertical-align: -15%;
+}
+.fa-2x {
+  font-size: 2em;
+}
+.fa-3x {
+  font-size: 3em;
+}
+.fa-4x {
+  font-size: 4em;
+}
+.fa-5x {
+  font-size: 5em;
+}
+.fa-fw {
+  width: 1.28571429em;
+  text-align: center;
+}
+.fa-ul {
+  padding-left: 0;
+  margin-left: 2.14285714em;
+  list-style-type: none;
+}
+.fa-ul > li {
+  position: relative;
+}
+.fa-li {
+  position: absolute;
+  left: -2.14285714em;
+  width: 2.14285714em;
+  top: 0.14285714em;
+  text-align: center;
+}
+.fa-li.fa-lg {
+  left: -1.85714286em;
+}
+.fa-border {
+  padding: .2em .25em .15em;
+  border: solid 0.08em #eeeeee;
+  border-radius: .1em;
+}
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.fa.pull-left {
+  margin-right: .3em;
+}
+.fa.pull-right {
+  margin-left: .3em;
+}
+.fa-spin {
+  -webkit-animation: spin 2s infinite linear;
+  -moz-animation: spin 2s infinite linear;
+  -o-animation: spin 2s infinite linear;
+  animation: spin 2s infinite linear;
+}
+@-moz-keyframes spin {
+  0% {
+    -moz-transform: rotate(0deg);
+  }
+  100% {
+    -moz-transform: rotate(359deg);
+  }
+}
+@-webkit-keyframes spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+  }
+}
+@-o-keyframes spin {
+  0% {
+    -o-transform: rotate(0deg);
+  }
+  100% {
+    -o-transform: rotate(359deg);
+  }
+}
+@keyframes spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+.fa-rotate-90 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+  -webkit-transform: rotate(90deg);
+  -moz-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  -o-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.fa-rotate-180 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  -webkit-transform: rotate(180deg);
+  -moz-transform: rotate(180deg);
+  -ms-transform: rotate(180deg);
+  -o-transform: rotate(180deg);
+  transform: rotate(180deg);
+}
+.fa-rotate-270 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+  -webkit-transform: rotate(270deg);
+  -moz-transform: rotate(270deg);
+  -ms-transform: rotate(270deg);
+  -o-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
+  -webkit-transform: scale(-1, 1);
+  -moz-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  -o-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
+  -webkit-transform: scale(1, -1);
+  -moz-transform: scale(1, -1);
+  -ms-transform: scale(1, -1);
+  -o-transform: scale(1, -1);
+  transform: scale(1, -1);
+}
+.fa-stack {
+  position: relative;
+  display: inline-block;
+  width: 2em;
+  height: 2em;
+  line-height: 2em;
+  vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+}
+.fa-stack-1x {
+  line-height: inherit;
+}
+.fa-stack-2x {
+  font-size: 2em;
+}
+.fa-inverse {
+  color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+   readers do not read off random characters that represent icons */
+.fa-glass:before {
+  content: "\f000";
+}
+.fa-music:before {
+  content: "\f001";
+}
+.fa-search:before {
+  content: "\f002";
+}
+.fa-envelope-o:before {
+  content: "\f003";
+}
+.fa-heart:before {
+  content: "\f004";
+}
+.fa-star:before {
+  content: "\f005";
+}
+.fa-star-o:before {
+  content: "\f006";
+}
+.fa-user:before {
+  content: "\f007";
+}
+.fa-film:before {
+  content: "\f008";
+}
+.fa-th-large:before {
+  content: "\f009";
+}
+.fa-th:before {
+  content: "\f00a";
+}
+.fa-th-list:before {
+  content: "\f00b";
+}
+.fa-check:before {
+  content: "\f00c";
+}
+.fa-times:before {
+  content: "\f00d";
+}
+.fa-search-plus:before {
+  content: "\f00e";
+}
+.fa-search-minus:before {
+  content: "\f010";
+}
+.fa-power-off:before {
+  content: "\f011";
+}
+.fa-signal:before {
+  content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+  content: "\f013";
+}
+.fa-trash-o:before {
+  content: "\f014";
+}
+.fa-home:before {
+  content: "\f015";
+}
+.fa-file-o:before {
+  content: "\f016";
+}
+.fa-clock-o:before {
+  content: "\f017";
+}
+.fa-road:before {
+  content: "\f018";
+}
+.fa-download:before {
+  content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+  content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+  content: "\f01b";
+}
+.fa-inbox:before {
+  content: "\f01c";
+}
+.fa-play-circle-o:before {
+  content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+  content: "\f01e";
+}
+.fa-refresh:before {
+  content: "\f021";
+}
+.fa-list-alt:before {
+  content: "\f022";
+}
+.fa-lock:before {
+  content: "\f023";
+}
+.fa-flag:before {
+  content: "\f024";
+}
+.fa-headphones:before {
+  content: "\f025";
+}
+.fa-volume-off:before {
+  content: "\f026";
+}
+.fa-volume-down:before {
+  content: "\f027";
+}
+.fa-volume-up:before {
+  content: "\f028";
+}
+.fa-qrcode:before {
+  content: "\f029";
+}
+.fa-barcode:before {
+  content: "\f02a";
+}
+.fa-tag:before {
+  content: "\f02b";
+}
+.fa-tags:before {
+  content: "\f02c";
+}
+.fa-book:before {
+  content: "\f02d";
+}
+.fa-bookmark:before {
+  content: "\f02e";
+}
+.fa-print:before {
+  content: "\f02f";
+}
+.fa-camera:before {
+  content: "\f030";
+}
+.fa-font:before {
+  content: "\f031";
+}
+.fa-bold:before {
+  content: "\f032";
+}
+.fa-italic:before {
+  content: "\f033";
+}
+.fa-text-height:before {
+  content: "\f034";
+}
+.fa-text-width:before {
+  content: "\f035";
+}
+.fa-align-left:before {
+  content: "\f036";
+}
+.fa-align-center:before {
+  content: "\f037";
+}
+.fa-align-right:before {
+  content: "\f038";
+}
+.fa-align-justify:before {
+  content: "\f039";
+}
+.fa-list:before {
+  content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+  content: "\f03b";
+}
+.fa-indent:before {
+  content: "\f03c";
+}
+.fa-video-camera:before {
+  content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+  content: "\f03e";
+}
+.fa-pencil:before {
+  content: "\f040";
+}
+.fa-map-marker:before {
+  content: "\f041";
+}
+.fa-adjust:before {
+  content: "\f042";
+}
+.fa-tint:before {
+  content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+  content: "\f044";
+}
+.fa-share-square-o:before {
+  content: "\f045";
+}
+.fa-check-square-o:before {
+  content: "\f046";
+}
+.fa-arrows:before {
+  content: "\f047";
+}
+.fa-step-backward:before {
+  content: "\f048";
+}
+.fa-fast-backward:before {
+  content: "\f049";
+}
+.fa-backward:before {
+  content: "\f04a";
+}
+.fa-play:before {
+  content: "\f04b";
+}
+.fa-pause:before {
+  content: "\f04c";
+}
+.fa-stop:before {
+  content: "\f04d";
+}
+.fa-forward:before {
+  content: "\f04e";
+}
+.fa-fast-forward:before {
+  content: "\f050";
+}
+.fa-step-forward:before {
+  content: "\f051";
+}
+.fa-eject:before {
+  content: "\f052";
+}
+.fa-chevron-left:before {
+  content: "\f053";
+}
+.fa-chevron-right:before {
+  content: "\f054";
+}
+.fa-plus-circle:before {
+  content: "\f055";
+}
+.fa-minus-circle:before {
+  content: "\f056";
+}
+.fa-times-circle:before {
+  content: "\f057";
+}
+.fa-check-circle:before {
+  content: "\f058";
+}
+.fa-question-circle:before {
+  content: "\f059";
+}
+.fa-info-circle:before {
+  content: "\f05a";
+}
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+.fa-times-circle-o:before {
+  content: "\f05c";
+}
+.fa-check-circle-o:before {
+  content: "\f05d";
+}
+.fa-ban:before {
+  content: "\f05e";
+}
+.fa-arrow-left:before {
+  content: "\f060";
+}
+.fa-arrow-right:before {
+  content: "\f061";
+}
+.fa-arrow-up:before {
+  content: "\f062";
+}
+.fa-arrow-down:before {
+  content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+  content: "\f064";
+}
+.fa-expand:before {
+  content: "\f065";
+}
+.fa-compress:before {
+  content: "\f066";
+}
+.fa-plus:before {
+  content: "\f067";
+}
+.fa-minus:before {
+  content: "\f068";
+}
+.fa-asterisk:before {
+  content: "\f069";
+}
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+.fa-gift:before {
+  content: "\f06b";
+}
+.fa-leaf:before {
+  content: "\f06c";
+}
+.fa-fire:before {
+  content: "\f06d";
+}
+.fa-eye:before {
+  content: "\f06e";
+}
+.fa-eye-slash:before {
+  content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+.fa-plane:before {
+  content: "\f072";
+}
+.fa-calendar:before {
+  content: "\f073";
+}
+.fa-random:before {
+  content: "\f074";
+}
+.fa-comment:before {
+  content: "\f075";
+}
+.fa-magnet:before {
+  content: "\f076";
+}
+.fa-chevron-up:before {
+  content: "\f077";
+}
+.fa-chevron-down:before {
+  content: "\f078";
+}
+.fa-retweet:before {
+  content: "\f079";
+}
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+.fa-folder:before {
+  content: "\f07b";
+}
+.fa-folder-open:before {
+  content: "\f07c";
+}
+.fa-arrows-v:before {
+  content: "\f07d";
+}
+.fa-arrows-h:before {
+  content: "\f07e";
+}
+.fa-bar-chart-o:before {
+  content: "\f080";
+}
+.fa-twitter-square:before {
+  content: "\f081";
+}
+.fa-facebook-square:before {
+  content: "\f082";
+}
+.fa-camera-retro:before {
+  content: "\f083";
+}
+.fa-key:before {
+  content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+  content: "\f085";
+}
+.fa-comments:before {
+  content: "\f086";
+}
+.fa-thumbs-o-up:before {
+  content: "\f087";
+}
+.fa-thumbs-o-down:before {
+  content: "\f088";
+}
+.fa-star-half:before {
+  content: "\f089";
+}
+.fa-heart-o:before {
+  content: "\f08a";
+}
+.fa-sign-out:before {
+  content: "\f08b";
+}
+.fa-linkedin-square:before {
+  content: "\f08c";
+}
+.fa-thumb-tack:before {
+  content: "\f08d";
+}
+.fa-external-link:before {
+  content: "\f08e";
+}
+.fa-sign-in:before {
+  content: "\f090";
+}
+.fa-trophy:before {
+  content: "\f091";
+}
+.fa-github-square:before {
+  content: "\f092";
+}
+.fa-upload:before {
+  content: "\f093";
+}
+.fa-lemon-o:before {
+  content: "\f094";
+}
+.fa-phone:before {
+  content: "\f095";
+}
+.fa-square-o:before {
+  content: "\f096";
+}
+.fa-bookmark-o:before {
+  content: "\f097";
+}
+.fa-phone-square:before {
+  content: "\f098";
+}
+.fa-twitter:before {
+  content: "\f099";
+}
+.fa-facebook:before {
+  content: "\f09a";
+}
+.fa-github:before {
+  content: "\f09b";
+}
+.fa-unlock:before {
+  content: "\f09c";
+}
+.fa-credit-card:before {
+  content: "\f09d";
+}
+.fa-rss:before {
+  content: "\f09e";
+}
+.fa-hdd-o:before {
+  content: "\f0a0";
+}
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+.fa-bell:before {
+  content: "\f0f3";
+}
+.fa-certificate:before {
+  content: "\f0a3";
+}
+.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+.fa-globe:before {
+  content: "\f0ac";
+}
+.fa-wrench:before {
+  content: "\f0ad";
+}
+.fa-tasks:before {
+  content: "\f0ae";
+}
+.fa-filter:before {
+  content: "\f0b0";
+}
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+  content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+  content: "\f0c1";
+}
+.fa-cloud:before {
+  content: "\f0c2";
+}
+.fa-flask:before {
+  content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+  content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+  content: "\f0c5";
+}
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+  content: "\f0c7";
+}
+.fa-square:before {
+  content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+  content: "\f0c9";
+}
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+.fa-underline:before {
+  content: "\f0cd";
+}
+.fa-table:before {
+  content: "\f0ce";
+}
+.fa-magic:before {
+  content: "\f0d0";
+}
+.fa-truck:before {
+  content: "\f0d1";
+}
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+.fa-google-plus:before {
+  content: "\f0d5";
+}
+.fa-money:before {
+  content: "\f0d6";
+}
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+.fa-caret-right:before {
+  content: "\f0da";
+}
+.fa-columns:before {
+  content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+  content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+  content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+  content: "\f0de";
+}
+.fa-envelope:before {
+  content: "\f0e0";
+}
+.fa-linkedin:before {
+  content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+  content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+  content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+  content: "\f0e4";
+}
+.fa-comment-o:before {
+  content: "\f0e5";
+}
+.fa-comments-o:before {
+  content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+  content: "\f0e7";
+}
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+  content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+.fa-exchange:before {
+  content: "\f0ec";
+}
+.fa-cloud-download:before {
+  content: "\f0ed";
+}
+.fa-cloud-upload:before {
+  content: "\f0ee";
+}
+.fa-user-md:before {
+  content: "\f0f0";
+}
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+.fa-bell-o:before {
+  content: "\f0a2";
+}
+.fa-coffee:before {
+  content: "\f0f4";
+}
+.fa-cutlery:before {
+  content: "\f0f5";
+}
+.fa-file-text-o:before {
+  content: "\f0f6";
+}
+.fa-building-o:before {
+  content: "\f0f7";
+}
+.fa-hospital-o:before {
+  content: "\f0f8";
+}
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+.fa-medkit:before {
+  content: "\f0fa";
+}
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+.fa-beer:before {
+  content: "\f0fc";
+}
+.fa-h-square:before {
+  content: "\f0fd";
+}
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+.fa-angle-left:before {
+  content: "\f104";
+}
+.fa-angle-right:before {
+  content: "\f105";
+}
+.fa-angle-up:before {
+  content: "\f106";
+}
+.fa-angle-down:before {
+  content: "\f107";
+}
+.fa-desktop:before {
+  content: "\f108";
+}
+.fa-laptop:before {
+  content: "\f109";
+}
+.fa-tablet:before {
+  content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+  content: "\f10b";
+}
+.fa-circle-o:before {
+  content: "\f10c";
+}
+.fa-quote-left:before {
+  content: "\f10d";
+}
+.fa-quote-right:before {
+  content: "\f10e";
+}
+.fa-spinner:before {
+  content: "\f110";
+}
+.fa-circle:before {
+  content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+  content: "\f112";
+}
+.fa-github-alt:before {
+  content: "\f113";
+}
+.fa-folder-o:before {
+  content: "\f114";
+}
+.fa-folder-open-o:before {
+  content: "\f115";
+}
+.fa-smile-o:before {
+  content: "\f118";
+}
+.fa-frown-o:before {
+  content: "\f119";
+}
+.fa-meh-o:before {
+  content: "\f11a";
+}
+.fa-gamepad:before {
+  content: "\f11b";
+}
+.fa-keyboard-o:before {
+  content: "\f11c";
+}
+.fa-flag-o:before {
+  content: "\f11d";
+}
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+.fa-terminal:before {
+  content: "\f120";
+}
+.fa-code:before {
+  content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+  content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+  content: "\f123";
+}
+.fa-location-arrow:before {
+  content: "\f124";
+}
+.fa-crop:before {
+  content: "\f125";
+}
+.fa-code-fork:before {
+  content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+  content: "\f127";
+}
+.fa-question:before {
+  content: "\f128";
+}
+.fa-info:before {
+  content: "\f129";
+}
+.fa-exclamation:before {
+  content: "\f12a";
+}
+.fa-superscript:before {
+  content: "\f12b";
+}
+.fa-subscript:before {
+  content: "\f12c";
+}
+.fa-eraser:before {
+  content: "\f12d";
+}
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+.fa-microphone:before {
+  content: "\f130";
+}
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+.fa-shield:before {
+  content: "\f132";
+}
+.fa-calendar-o:before {
+  content: "\f133";
+}
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+.fa-rocket:before {
+  content: "\f135";
+}
+.fa-maxcdn:before {
+  content: "\f136";
+}
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+.fa-html5:before {
+  content: "\f13b";
+}
+.fa-css3:before {
+  content: "\f13c";
+}
+.fa-anchor:before {
+  content: "\f13d";
+}
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+.fa-bullseye:before {
+  content: "\f140";
+}
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+.fa-rss-square:before {
+  content: "\f143";
+}
+.fa-play-circle:before {
+  content: "\f144";
+}
+.fa-ticket:before {
+  content: "\f145";
+}
+.fa-minus-square:before {
+  content: "\f146";
+}
+.fa-minus-square-o:before {
+  content: "\f147";
+}
+.fa-level-up:before {
+  content: "\f148";
+}
+.fa-level-down:before {
+  content: "\f149";
+}
+.fa-check-square:before {
+  content: "\f14a";
+}
+.fa-pencil-square:before {
+  content: "\f14b";
+}
+.fa-external-link-square:before {
+  content: "\f14c";
+}
+.fa-share-square:before {
+  content: "\f14d";
+}
+.fa-compass:before {
+  content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+  content: "\f153";
+}
+.fa-gbp:before {
+  content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+  content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+  content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+  content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+  content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+  content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+  content: "\f15a";
+}
+.fa-file:before {
+  content: "\f15b";
+}
+.fa-file-text:before {
+  content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+  content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+.fa-sort-amount-desc:before {
+  content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+  content: "\f163";
+}
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+.fa-youtube-square:before {
+  content: "\f166";
+}
+.fa-youtube:before {
+  content: "\f167";
+}
+.fa-xing:before {
+  content: "\f168";
+}
+.fa-xing-square:before {
+  content: "\f169";
+}
+.fa-youtube-play:before {
+  content: "\f16a";
+}
+.fa-dropbox:before {
+  content: "\f16b";
+}
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+.fa-instagram:before {
+  content: "\f16d";
+}
+.fa-flickr:before {
+  content: "\f16e";
+}
+.fa-adn:before {
+  content: "\f170";
+}
+.fa-bitbucket:before {
+  content: "\f171";
+}
+.fa-bitbucket-square:before {
+  content: "\f172";
+}
+.fa-tumblr:before {
+  content: "\f173";
+}
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+.fa-long-arrow-down:before {
+  content: "\f175";
+}
+.fa-long-arrow-up:before {
+  content: "\f176";
+}
+.fa-long-arrow-left:before {
+  content: "\f177";
+}
+.fa-long-arrow-right:before {
+  content: "\f178";
+}
+.fa-apple:before {
+  content: "\f179";
+}
+.fa-windows:before {
+  content: "\f17a";
+}
+.fa-android:before {
+  content: "\f17b";
+}
+.fa-linux:before {
+  content: "\f17c";
+}
+.fa-dribbble:before {
+  content: "\f17d";
+}
+.fa-skype:before {
+  content: "\f17e";
+}
+.fa-foursquare:before {
+  content: "\f180";
+}
+.fa-trello:before {
+  content: "\f181";
+}
+.fa-female:before {
+  content: "\f182";
+}
+.fa-male:before {
+  content: "\f183";
+}
+.fa-gittip:before {
+  content: "\f184";
+}
+.fa-sun-o:before {
+  content: "\f185";
+}
+.fa-moon-o:before {
+  content: "\f186";
+}
+.fa-archive:before {
+  content: "\f187";
+}
+.fa-bug:before {
+  content: "\f188";
+}
+.fa-vk:before {
+  content: "\f189";
+}
+.fa-weibo:before {
+  content: "\f18a";
+}
+.fa-renren:before {
+  content: "\f18b";
+}
+.fa-pagelines:before {
+  content: "\f18c";
+}
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+  content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+  content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+.fa-dot-circle-o:before {
+  content: "\f192";
+}
+.fa-wheelchair:before {
+  content: "\f193";
+}
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+  content: "\f195";
+}
+.fa-plus-square-o:before {
+  content: "\f196";
+}
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+.fa-slack:before {
+  content: "\f198";
+}
+.fa-envelope-square:before {
+  content: "\f199";
+}
+.fa-wordpress:before {
+  content: "\f19a";
+}
+.fa-openid:before {
+  content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+  content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+.fa-yahoo:before {
+  content: "\f19e";
+}
+.fa-google:before {
+  content: "\f1a0";
+}
+.fa-reddit:before {
+  content: "\f1a1";
+}
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+.fa-delicious:before {
+  content: "\f1a5";
+}
+.fa-digg:before {
+  content: "\f1a6";
+}
+.fa-pied-piper-square:before,
+.fa-pied-piper:before {
+  content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+.fa-drupal:before {
+  content: "\f1a9";
+}
+.fa-joomla:before {
+  content: "\f1aa";
+}
+.fa-language:before {
+  content: "\f1ab";
+}
+.fa-fax:before {
+  content: "\f1ac";
+}
+.fa-building:before {
+  content: "\f1ad";
+}
+.fa-child:before {
+  content: "\f1ae";
+}
+.fa-paw:before {
+  content: "\f1b0";
+}
+.fa-spoon:before {
+  content: "\f1b1";
+}
+.fa-cube:before {
+  content: "\f1b2";
+}
+.fa-cubes:before {
+  content: "\f1b3";
+}
+.fa-behance:before {
+  content: "\f1b4";
+}
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+.fa-steam:before {
+  content: "\f1b6";
+}
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+.fa-recycle:before {
+  content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+  content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+  content: "\f1ba";
+}
+.fa-tree:before {
+  content: "\f1bb";
+}
+.fa-spotify:before {
+  content: "\f1bc";
+}
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+.fa-database:before {
+  content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+.fa-file-word-o:before {
+  content: "\f1c2";
+}
+.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+  content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+  content: "\f1c8";
+}
+.fa-file-code-o:before {
+  content: "\f1c9";
+}
+.fa-vine:before {
+  content: "\f1ca";
+}
+.fa-codepen:before {
+  content: "\f1cb";
+}
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+.fa-ra:before,
+.fa-rebel:before {
+  content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+  content: "\f1d1";
+}
+.fa-git-square:before {
+  content: "\f1d2";
+}
+.fa-git:before {
+  content: "\f1d3";
+}
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+.fa-qq:before {
+  content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+  content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+  content: "\f1d9";
+}
+.fa-history:before {
+  content: "\f1da";
+}
+.fa-circle-thin:before {
+  content: "\f1db";
+}
+.fa-header:before {
+  content: "\f1dc";
+}
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+.fa-sliders:before {
+  content: "\f1de";
+}
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+.fa-bomb:before {
+  content: "\f1e2";
+}
index 7ec25e401456ff6bb4b38283e2636c2965d51dde..a0535142552a49c2b4b62131a391400e8bdc6828 100644 (file)
 }
 
 .spaced-out li {
-   padding-bottom: 1em; 
+   padding-bottom: 1em;
+}
+
+.inside-list ul {
+    list-style-position: inside;
+    padding-left: 0;
 }
\ No newline at end of file
diff --git a/doc/fonts/FontAwesome.otf b/doc/fonts/FontAwesome.otf
new file mode 100644 (file)
index 0000000..3461e3f
Binary files /dev/null and b/doc/fonts/FontAwesome.otf differ
diff --git a/doc/fonts/fontawesome-webfont.eot b/doc/fonts/fontawesome-webfont.eot
new file mode 100755 (executable)
index 0000000..6cfd566
Binary files /dev/null and b/doc/fonts/fontawesome-webfont.eot differ
diff --git a/doc/fonts/fontawesome-webfont.svg b/doc/fonts/fontawesome-webfont.svg
new file mode 100755 (executable)
index 0000000..a9f8469
--- /dev/null
@@ -0,0 +1,504 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" "  horiz-adv-x="448" />
+<glyph unicode="&#x09;" horiz-adv-x="448" />
+<glyph unicode="&#xa0;" horiz-adv-x="448" />
+<glyph unicode="&#xa8;" horiz-adv-x="1792" />
+<glyph unicode="&#xa9;" horiz-adv-x="1792" />
+<glyph unicode="&#xae;" horiz-adv-x="1792" />
+<glyph unicode="&#xb4;" horiz-adv-x="1792" />
+<glyph unicode="&#xc6;" horiz-adv-x="1792" />
+<glyph unicode="&#xd8;" horiz-adv-x="1792" />
+<glyph unicode="&#x2000;" horiz-adv-x="768" />
+<glyph unicode="&#x2001;" horiz-adv-x="1537" />
+<glyph unicode="&#x2002;" horiz-adv-x="768" />
+<glyph unicode="&#x2003;" horiz-adv-x="1537" />
+<glyph unicode="&#x2004;" horiz-adv-x="512" />
+<glyph unicode="&#x2005;" horiz-adv-x="384" />
+<glyph unicode="&#x2006;" horiz-adv-x="256" />
+<glyph unicode="&#x2007;" horiz-adv-x="256" />
+<glyph unicode="&#x2008;" horiz-adv-x="192" />
+<glyph unicode="&#x2009;" horiz-adv-x="307" />
+<glyph unicode="&#x200a;" horiz-adv-x="85" />
+<glyph unicode="&#x202f;" horiz-adv-x="307" />
+<glyph unicode="&#x205f;" horiz-adv-x="384" />
+<glyph unicode="&#x2122;" horiz-adv-x="1792" />
+<glyph unicode="&#x221e;" horiz-adv-x="1792" />
+<glyph unicode="&#x2260;" horiz-adv-x="1792" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xf000;" horiz-adv-x="1792" d="M93 1350q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78z" />
+<glyph unicode="&#xf001;" d="M0 -64q0 50 34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5 q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89z" />
+<glyph unicode="&#xf002;" horiz-adv-x="1664" d="M0 704q0 143 55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5z M256 704q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" />
+<glyph unicode="&#xf003;" horiz-adv-x="1792" d="M0 32v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113zM128 32q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5 t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768zM128 1120q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317 q54 43 100.5 115.5t46.5 131.5v11v13.5t-0.5 13t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5z" />
+<glyph unicode="&#xf004;" horiz-adv-x="1792" d="M0 940q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138z " />
+<glyph unicode="&#xf005;" horiz-adv-x="1664" d="M0 889q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48z" />
+<glyph unicode="&#xf006;" horiz-adv-x="1664" d="M0 889q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354 q-25 27 -25 48zM221 829l306 -297l-73 -421l378 199l377 -199l-72 421l306 297l-422 62l-189 382l-189 -382z" />
+<glyph unicode="&#xf007;" horiz-adv-x="1408" d="M0 131q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5q0 -120 -73 -189.5t-194 -69.5 h-874q-121 0 -194 69.5t-73 189.5zM320 1024q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5z" />
+<glyph unicode="&#xf008;" horiz-adv-x="1920" d="M0 -96v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113zM128 64v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45zM128 320q0 -26 19 -45t45 -19h128 q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128zM128 704q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128zM128 1088q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19 h-128q-26 0 -45 -19t-19 -45v-128zM512 -64q0 -26 19 -45t45 -19h768q26 0 45 19t19 45v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512zM512 704q0 -26 19 -45t45 -19h768q26 0 45 19t19 45v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512zM1536 64 v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45zM1536 320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128zM1536 704q0 -26 19 -45t45 -19h128q26 0 45 19t19 45 v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128zM1536 1088q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128z" />
+<glyph unicode="&#xf009;" horiz-adv-x="1664" d="M0 128v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90zM0 896v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90zM896 128v384q0 52 38 90t90 38h512q52 0 90 -38 t38 -90v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90zM896 896v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90z" />
+<glyph unicode="&#xf00a;" horiz-adv-x="1792" d="M0 96v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM0 608v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM0 1120v192q0 40 28 68t68 28h320q40 0 68 -28 t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM640 96v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM640 608v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68zM640 1120v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM1280 96v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM1280 608v192 q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM1280 1120v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf00b;" horiz-adv-x="1792" d="M0 96v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM0 608v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM0 1120v192q0 40 28 68t68 28h320q40 0 68 -28 t28 -68v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM640 96v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68zM640 608v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68zM640 1120v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf00c;" horiz-adv-x="1792" d="M121 608q0 40 28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68t-28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68z" />
+<glyph unicode="&#xf00d;" horiz-adv-x="1408" d="M110 214q0 40 28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68t-28 -68l-294 -294l294 -294q28 -28 28 -68t-28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294 q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68z" />
+<glyph unicode="&#xf00e;" horiz-adv-x="1664" d="M0 704q0 143 55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90t-37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5z M256 704q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5zM384 672v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224q13 0 22.5 -9.5t9.5 -22.5v-64 q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf010;" horiz-adv-x="1664" d="M0 704q0 143 55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90t-37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5z M256 704q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5zM384 672v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf011;" d="M0 640q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181 q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298zM640 768v640q0 52 38 90t90 38t90 -38t38 -90v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90z" />
+<glyph unicode="&#xf012;" horiz-adv-x="1792" d="M0 -96v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM384 -96v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM768 -96v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576 q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM1152 -96v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM1536 -96v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf013;" d="M0 531v222q0 12 8 23t19 13l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10 q129 -119 165 -170q7 -8 7 -22q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108 q-44 -23 -91 -38q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5z M512 640q0 -106 75 -181t181 -75t181 75t75 181t-75 181t-181 75t-181 -75t-75 -181z" />
+<glyph unicode="&#xf014;" horiz-adv-x="1408" d="M0 1056v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23zM256 76q0 -22 7 -40.5 t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5v948h-896v-948zM384 224v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM640 224v576q0 14 9 23t23 9h64 q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23zM896 224v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf015;" horiz-adv-x="1664" d="M26 636.5q1 13.5 11 21.5l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5zM256 64 v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf016;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22 v-376z" />
+<glyph unicode="&#xf017;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM512 544v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf018;" horiz-adv-x="1920" d="M50 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256 q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73zM809 540q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4z" />
+<glyph unicode="&#xf019;" horiz-adv-x="1664" d="M0 96v320q0 40 28 68t68 28h465l135 -136q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68zM325 985q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39q17 -41 -14 -70 l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70zM1152 192q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM1408 192q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf01a;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM418 620q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35z" />
+<glyph unicode="&#xf01b;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM416 672q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf01c;" d="M0 64v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552q25 -61 25 -123v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM197 576h316l95 -192h320l95 192h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8 t-2.5 -8z" />
+<glyph unicode="&#xf01d;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM512 320v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55t-32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56z" />
+<glyph unicode="&#xf01e;" d="M0 640q0 156 61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5 t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298z" />
+<glyph unicode="&#xf021;" d="M0 0v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129 q-19 -19 -45 -19t-45 19t-19 45zM18 800v7q65 268 270 434.5t480 166.5q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179 q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf022;" horiz-adv-x="1792" d="M0 160v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113zM128 160q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832z M256 288v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 544v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5z M256 800v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 288v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5z M512 544v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5zM512 800v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5z " />
+<glyph unicode="&#xf023;" horiz-adv-x="1152" d="M0 96v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68zM320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192z" />
+<glyph unicode="&#xf024;" horiz-adv-x="1792" d="M64 1280q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110zM320 320v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19 q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf025;" horiz-adv-x="1664" d="M0 650q0 151 67 291t179 242.5t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32 q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32 q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314z" />
+<glyph unicode="&#xf026;" horiz-adv-x="768" d="M0 448v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf027;" horiz-adv-x="1152" d="M0 448v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45zM908 464q0 21 12 35.5t29 25t34 23t29 35.5t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5 q15 0 25 -5q70 -27 112.5 -93t42.5 -142t-42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5z" />
+<glyph unicode="&#xf028;" horiz-adv-x="1664" d="M0 448v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45zM908 464q0 21 12 35.5t29 25t34 23t29 35.5t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5 q15 0 25 -5q70 -27 112.5 -93t42.5 -142t-42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5zM1008 228q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5 q140 -59 225 -188.5t85 -282.5t-85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45zM1109 -7q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19 q13 0 26 -5q211 -91 338 -283.5t127 -422.5t-127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf029;" horiz-adv-x="1408" d="M0 0v640h640v-640h-640zM0 768v640h640v-640h-640zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM256 256v128h128v-128h-128zM256 1024v128h128v-128h-128zM768 0v640h384v-128h128v128h128v-384h-384v128h-128v-384h-128zM768 768v640h640v-640h-640z M896 896h384v384h-384v-384zM1024 0v128h128v-128h-128zM1024 1024v128h128v-128h-128zM1280 0v128h128v-128h-128z" />
+<glyph unicode="&#xf02a;" horiz-adv-x="1792" d="M0 0v1408h63v-1408h-63zM94 1v1407h32v-1407h-32zM189 1v1407h31v-1407h-31zM346 1v1407h31v-1407h-31zM472 1v1407h62v-1407h-62zM629 1v1407h31v-1407h-31zM692 1v1407h31v-1407h-31zM755 1v1407h31v-1407h-31zM880 1v1407h63v-1407h-63zM1037 1v1407h63v-1407h-63z M1163 1v1407h63v-1407h-63zM1289 1v1407h63v-1407h-63zM1383 1v1407h63v-1407h-63zM1541 1v1407h94v-1407h-94zM1666 1v1407h32v-1407h-32zM1729 0v1408h63v-1408h-63z" />
+<glyph unicode="&#xf02b;" d="M0 864v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117zM192 1088q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf02c;" horiz-adv-x="1920" d="M0 864v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117zM192 1088q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5zM704 1408h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5z" />
+<glyph unicode="&#xf02d;" horiz-adv-x="1664" d="M10 184q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23 t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57 q38 -15 59 -43q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5zM575 1056 q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="&#xf02e;" horiz-adv-x="1280" d="M0 7v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62z" />
+<glyph unicode="&#xf02f;" horiz-adv-x="1664" d="M0 160v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v160h-224 q-13 0 -22.5 9.5t-9.5 22.5zM384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1408 576q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf030;" horiz-adv-x="1920" d="M0 128v896q0 106 75 181t181 75h224l51 136q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181zM512 576q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5zM672 576q0 119 84.5 203.5t203.5 84.5t203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5z" />
+<glyph unicode="&#xf031;" horiz-adv-x="1664" d="M0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8 t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27 q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14zM555 527q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452z" />
+<glyph unicode="&#xf032;" horiz-adv-x="1408" d="M0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5 t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12zM533 1292q0 -50 4 -151t4 -152q0 -27 -0.5 -80 t-0.5 -79q0 -46 1 -69q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13zM538.5 165q0.5 -37 4.5 -83.5t12 -66.5q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25 t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5z" />
+<glyph unicode="&#xf033;" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" />
+<glyph unicode="&#xf034;" horiz-adv-x="1792" d="M0 1023v383l81 1l54 -27q12 -5 211 -5q44 0 132 2t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5 q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9 t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44zM1414 109.5q9 18.5 42 18.5h80v1024 h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5z" />
+<glyph unicode="&#xf035;" d="M0 1023v383l81 1l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1 t-103 1t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29 t78 27q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44zM5 -64q0 28 26 49q4 3 36 30t59.5 49 t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5q12 0 42 -19.5t57.5 -41.5t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5 t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49z" />
+<glyph unicode="&#xf036;" horiz-adv-x="1792" d="M0 64v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 448v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45zM0 832v128q0 26 19 45t45 19h1536 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1536q-26 0 -45 19t-19 45zM0 1216v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf037;" horiz-adv-x="1792" d="M0 64v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM128 832v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM384 448v128q0 26 19 45t45 19h896 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45zM512 1216v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf038;" horiz-adv-x="1792" d="M0 64v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM128 832v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1536q-26 0 -45 19t-19 45zM384 448v128q0 26 19 45t45 19h1280 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45zM512 1216v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf039;" horiz-adv-x="1792" d="M0 64v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 448v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 832v128q0 26 19 45t45 19h1664 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 1216v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf03a;" horiz-adv-x="1792" d="M0 32v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5zM0 416v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5zM0 800v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5zM0 1184v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192 q-13 0 -22.5 9.5t-9.5 22.5zM384 32v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5zM384 416v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5zM384 800v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5zM384 1184v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5v-192 q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf03b;" horiz-adv-x="1792" d="M0 32v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5zM0 1184v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5 t-9.5 22.5zM32 704q0 14 9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23zM640 416v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088 q-13 0 -22.5 9.5t-9.5 22.5zM640 800v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf03c;" horiz-adv-x="1792" d="M0 32v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5zM0 416v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23t-9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5z M0 1184v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5zM640 416v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5 t-9.5 22.5zM640 800v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf03d;" horiz-adv-x="1792" d="M0 288v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5q39 -17 39 -59v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5 t-84.5 203.5z" />
+<glyph unicode="&#xf03e;" horiz-adv-x="1920" d="M0 32v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113zM128 32q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216z M256 128v192l320 320l160 -160l512 512l416 -416v-448h-1408zM256 960q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136z" />
+<glyph unicode="&#xf040;" d="M0 -128v416l832 832l416 -416l-832 -832h-416zM128 128h128v-128h107l91 91l-235 235l-91 -91v-107zM298 384q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17zM896 1184l166 165q36 38 90 38q53 0 91 -38l235 -234 q37 -39 37 -91q0 -53 -37 -90l-166 -166z" />
+<glyph unicode="&#xf041;" horiz-adv-x="1024" d="M0 896q0 212 150 362t362 150t362 -150t150 -362q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179zM256 896q0 -106 75 -181t181 -75t181 75t75 181t-75 181t-181 75t-181 -75t-75 -181z" />
+<glyph unicode="&#xf042;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73v1088q-148 0 -273 -73t-198 -198t-73 -273z" />
+<glyph unicode="&#xf043;" horiz-adv-x="1024" d="M0 512q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275q0 -212 -150 -362t-362 -150t-362 150t-150 362zM256 384q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69z" />
+<glyph unicode="&#xf044;" horiz-adv-x="1792" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29v-190 q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM640 256v288l672 672l288 -288l-672 -672h-288zM736 448h96v-96h56l116 116l-152 152l-116 -116v-56zM944 688q16 -16 33 1l350 350q17 17 1 33t-33 -1l-350 -350q-17 -17 -1 -33zM1376 1280l92 92 q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68l-92 -92z" />
+<glyph unicode="&#xf045;" horiz-adv-x="1664" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h255q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29v-259 q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM256 704q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45l-384 -384 q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5t-38.5 114t-17.5 122z" />
+<glyph unicode="&#xf046;" horiz-adv-x="1664" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3 q20 -8 20 -29v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM257 768q0 33 24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110q24 -24 24 -57t-24 -57l-814 -814q-24 -24 -57 -24t-57 24l-430 430 q-24 24 -24 57z" />
+<glyph unicode="&#xf047;" horiz-adv-x="1792" d="M0 640q0 26 19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45t-19 -45l-256 -256 q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45z" />
+<glyph unicode="&#xf048;" horiz-adv-x="1024" d="M0 -64v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf049;" horiz-adv-x="1792" d="M0 -64v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710q19 19 32 13t13 -32v-710q4 11 13 19l710 710q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45 t-45 -19h-128q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf04a;" horiz-adv-x="1664" d="M122 640q0 26 19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19l710 710q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45z" />
+<glyph unicode="&#xf04b;" horiz-adv-x="1408" d="M0 -96v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31l-1328 -738q-23 -13 -39.5 -3t-16.5 36z" />
+<glyph unicode="&#xf04c;" d="M0 -64v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45zM896 -64v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf04d;" d="M0 -64v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf04e;" horiz-adv-x="1664" d="M0 -96v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19l-710 -710q-19 -19 -32 -13t-13 32z" />
+<glyph unicode="&#xf050;" horiz-adv-x="1792" d="M0 -96v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710q-19 -19 -32 -13t-13 32v710 q-5 -10 -13 -19l-710 -710q-19 -19 -32 -13t-13 32z" />
+<glyph unicode="&#xf051;" horiz-adv-x="1024" d="M0 -96v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710q-19 -19 -32 -13t-13 32z" />
+<glyph unicode="&#xf052;" horiz-adv-x="1538" d="M1 64v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM1 525q-6 13 13 32l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13z" />
+<glyph unicode="&#xf053;" horiz-adv-x="1280" d="M154 704q0 26 19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45z" />
+<glyph unicode="&#xf054;" horiz-adv-x="1280" d="M90 128q0 26 19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45z" />
+<glyph unicode="&#xf055;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM320 576q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19 t19 45v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128z" />
+<glyph unicode="&#xf056;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM320 576q0 -26 19 -45t45 -19h768q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19 t-19 -45v-128z" />
+<glyph unicode="&#xf057;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM387 414q0 -27 19 -46l90 -90q19 -19 46 -19q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19 l90 90q19 19 19 46q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45z" />
+<glyph unicode="&#xf058;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM252 621q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45q0 28 -18 46l-91 90 q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46z" />
+<glyph unicode="&#xf059;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM417 939q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26 t37.5 -59q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213zM640 160q0 -14 9 -23t23 -9 h192q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192z" />
+<glyph unicode="&#xf05a;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM512 160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320 q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160zM640 1056q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160z" />
+<glyph unicode="&#xf05b;" d="M0 576v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143 q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45zM339 512q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5h-109q-26 0 -45 19 t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109z" />
+<glyph unicode="&#xf05c;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM429 480q0 13 10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23l-137 -137l137 -137q10 -10 10 -23t-10 -23l-146 -146q-10 -10 -23 -10t-23 10l-137 137 l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23z" />
+<glyph unicode="&#xf05d;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM346 640q0 26 19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45z" />
+<glyph unicode="&#xf05e;" d="M0 643q0 157 61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5t-61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61t-245 164t-163.5 246t-61 300zM224 643q0 -162 89 -299l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199 t-73 -274zM471 185q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5q0 161 -87 295z" />
+<glyph unicode="&#xf060;" d="M64 576q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5t32.5 -90.5v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90 z" />
+<glyph unicode="&#xf061;" d="M0 512v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5z" />
+<glyph unicode="&#xf062;" horiz-adv-x="1664" d="M53 565q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651q37 -39 37 -91q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75 q-38 38 -38 90z" />
+<glyph unicode="&#xf063;" horiz-adv-x="1664" d="M53 704q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90z" />
+<glyph unicode="&#xf064;" horiz-adv-x="1792" d="M0 416q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45t-19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123 q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22t-13.5 30t-10.5 24q-127 285 -127 451z" />
+<glyph unicode="&#xf065;" d="M0 -64v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23t-10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45zM781 800q0 13 10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448 q26 0 45 -19t19 -45v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23z" />
+<glyph unicode="&#xf066;" d="M13 32q0 13 10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23zM768 704v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10 t23 -10l114 -114q10 -10 10 -23t-10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf067;" horiz-adv-x="1408" d="M0 608v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf068;" horiz-adv-x="1408" d="M0 608v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf069;" horiz-adv-x="1664" d="M122.5 408.5q13.5 51.5 59.5 77.5l266 154l-266 154q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5 l-266 -154l266 -154q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5z" />
+<glyph unicode="&#xf06a;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM624 1126l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5l18 621q0 12 -10 18 q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18zM640 161q0 -13 10 -23t23 -10h192q13 0 22 9.5t9 23.5v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190z" />
+<glyph unicode="&#xf06b;" d="M0 544v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68 t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23zM376 1120q0 -40 28 -68t68 -28h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68zM608 180q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5v56v468v192h-320v-192v-468v-56zM870 1024h194q40 0 68 28 t28 68t-28 68t-68 28q-43 0 -69 -31z" />
+<glyph unicode="&#xf06c;" horiz-adv-x="1792" d="M0 121q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96 q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5zM384 448q0 -26 19 -45t45 -19q24 0 45 19 q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45t-19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45z" />
+<glyph unicode="&#xf06d;" horiz-adv-x="1408" d="M0 -160q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64zM256 640q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100 t113.5 -122.5t72.5 -150.5t27.5 -184q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184z" />
+<glyph unicode="&#xf06e;" horiz-adv-x="1792" d="M0 576q0 34 20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69t-20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69zM128 576q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5q-152 236 -381 353 q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353zM592 704q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34t-14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5z" />
+<glyph unicode="&#xf070;" horiz-adv-x="1792" d="M0 576q0 38 20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5q16 -10 16 -27q0 -7 -1 -9q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87 q-143 65 -263.5 173t-208.5 245q-20 31 -20 69zM128 576q167 -258 427 -375l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353zM592 704q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34t-14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5zM896 0l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69t-20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95zM1056 286l280 502q8 -45 8 -84q0 -139 -79 -253.5t-209 -164.5z" />
+<glyph unicode="&#xf071;" horiz-adv-x="1792" d="M16 61l768 1408q17 31 47 49t65 18t65 -18t47 -49l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126zM752 992l17 -457q0 -10 10 -16.5t24 -6.5h185q14 0 23.5 6.5t10.5 16.5l18 459q0 12 -10 19q-13 11 -24 11h-220 q-11 0 -24 -11q-10 -7 -10 -21zM768 161q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190z" />
+<glyph unicode="&#xf072;" horiz-adv-x="1408" d="M0 477q-1 13 9 25l96 97q9 9 23 9q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16 l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23z" />
+<glyph unicode="&#xf073;" horiz-adv-x="1664" d="M0 -128v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90zM128 -128h288v288h-288v-288zM128 224 h288v320h-288v-320zM128 608h288v288h-288v-288zM384 1088q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288zM480 -128h320v288h-320v-288zM480 224h320v320h-320v-320zM480 608h320v288h-320 v-288zM864 -128h320v288h-320v-288zM864 224h320v320h-320v-320zM864 608h320v288h-320v-288zM1152 1088q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288zM1248 -128h288v288h-288v-288z M1248 224h288v320h-288v-320zM1248 608h288v288h-288v-288z" />
+<glyph unicode="&#xf074;" horiz-adv-x="1792" d="M0 160v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23t-9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192 h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23zM0 1056v192q0 14 9 23t23 9h224q250 0 410 -225q-60 -92 -137 -273q-22 45 -37 72.5 t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23zM743 353q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23t-9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192 q-32 0 -85 -0.5t-81 -1t-73 1t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5z" />
+<glyph unicode="&#xf075;" horiz-adv-x="1792" d="M0 640q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5t-120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5 t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281z" />
+<glyph unicode="&#xf076;" d="M0 576v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5 t-98.5 362zM0 960v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45zM1024 960v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf077;" horiz-adv-x="1792" d="M90 250.5q0 26.5 19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5z" />
+<glyph unicode="&#xf078;" horiz-adv-x="1792" d="M90 773.5q0 26.5 19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5z" />
+<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M0 704q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45z M640 1120q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20z " />
+<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M0 1216q0 26 19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024 q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45zM384 0q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM1280 0q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5 t-90.5 -37.5t-90.5 37.5t-37.5 90.5z" />
+<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M0 224v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158z" />
+<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M0 224v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5t-0.5 12.5zM73 56q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43 q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43z" />
+<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M64 64q0 26 19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45z" />
+<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M0 640q0 26 19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45z" />
+<glyph unicode="&#xf080;" horiz-adv-x="1920" d="M0 32v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113zM128 32q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216z M256 128v384h256v-384h-256zM640 128v896h256v-896h-256zM1024 128v640h256v-640h-256zM1408 128v1024h256v-1024h-256z" />
+<glyph unicode="&#xf081;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 286q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109 q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4q21 -63 74.5 -104 t121.5 -42q-116 -90 -261 -90q-26 0 -50 3z" />
+<glyph unicode="&#xf082;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-192v608h203l30 224h-233v143q0 54 28 83t96 29l132 1v207q-96 9 -180 9q-136 0 -218 -80.5t-82 -225.5v-166h-224v-224h224v-608h-544 q-119 0 -203.5 84.5t-84.5 203.5z" />
+<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M0 0v1280q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5zM128 0h1536v128h-1536v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM256 1216h384v128h-384v-128zM512 574 q0 -159 112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5zM640 574q0 106 75 181t181 75t181 -75t75 -181t-75 -181t-181 -75t-181 75t-75 181zM736 576q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9 t9 23t-9 23t-23 9q-66 0 -113 -47t-47 -113z" />
+<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M0 752q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41q0 -17 -49 -66t-66 -49 q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5zM192 768q0 -80 56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56 t56 136t-56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136z" />
+<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M0 549v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8 q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90 q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5zM384 640q0 -106 75 -181t181 -75 t181 75t75 181t-75 181t-181 75t-181 -75t-75 -181zM1152 58v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31 v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31zM1152 1082v140q0 16 149 31q13 29 30 52 q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71 q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31zM1408 128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5q0 52 -38 90t-90 38t-90 -38t-38 -90zM1408 1152q0 -53 37.5 -90.5 t90.5 -37.5t90.5 37.5t37.5 90.5q0 52 -38 90t-90 38t-90 -38t-38 -90z" />
+<glyph unicode="&#xf086;" horiz-adv-x="1792" d="M0 768q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257t-94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25 t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224zM616 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5 t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132z" />
+<glyph unicode="&#xf087;" d="M0 128v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5zM128 192q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM384 128h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5 t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85 t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640z" />
+<glyph unicode="&#xf088;" d="M0 512v640q0 53 37.5 90.5t90.5 37.5h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186 q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5zM128 1088q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM384 512h32q16 0 35.5 -9 t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5 t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640z" />
+<glyph unicode="&#xf089;" horiz-adv-x="896" d="M0 889q0 37 56 46l502 73l225 455q19 41 49 41v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48z" />
+<glyph unicode="&#xf08a;" horiz-adv-x="1792" d="M0 940q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138z M128 940q0 -168 187 -355l581 -560l580 559q188 188 188 356q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5 t-21.5 -143z" />
+<glyph unicode="&#xf08b;" horiz-adv-x="1664" d="M0 288v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5q0 -4 1 -20t0.5 -26.5t-3 -23.5 t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5zM384 448v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45t-19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf08c;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM223 1030q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86z M237 122h231v694h-231v-694zM595 122h231v388q0 38 7 56q15 35 45 59.5t74 24.5q116 0 116 -157v-371h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694z" />
+<glyph unicode="&#xf08d;" horiz-adv-x="1152" d="M0 320q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19 t-19 45zM416 672q0 -14 9 -23t23 -9t23 9t9 23v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448z" />
+<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832 q-119 0 -203.5 84.5t-84.5 203.5zM685 576q0 13 10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23z" />
+<glyph unicode="&#xf090;" d="M0 448v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45t-19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45zM894.5 78.5q0.5 10.5 3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113 t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5q0 4 -1 20t-0.5 26.5z" />
+<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M0 928v128q0 40 28 68t68 28h288v96q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91t97.5 -37q75 0 133.5 -45.5 t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143zM128 928q0 -78 94.5 -162t235.5 -113q-74 162 -74 371 h-256v-96zM1206 653q141 29 235.5 113t94.5 162v96h-256q0 -209 -74 -371z" />
+<glyph unicode="&#xf092;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204 q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52 t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5h-224q-119 0 -203.5 84.5t-84.5 203.5zM271 315q3 5 13 2 q10 -5 7 -12q-5 -7 -13 -2q-10 5 -7 12zM304 290q6 6 16 -3q9 -11 2 -16q-6 -7 -16 3q-9 11 -2 16zM335 233q-9 13 0 18q9 7 17 -6q9 -12 0 -19q-8 -6 -17 7zM370 206q8 9 20 -3q12 -11 4 -19q-8 -9 -20 3q-13 11 -4 19zM419 168q4 11 19 7q16 -5 13 -16q-4 -12 -19 -6 q-17 4 -13 15zM481 154q0 11 16 11q17 2 17 -11q0 -11 -16 -11q-17 -2 -17 11zM540 158q-2 12 14 15q16 2 18 -9q2 -10 -14 -14t-18 8z" />
+<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M0 -32v320q0 40 28 68t68 28h427q21 -56 70.5 -92t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68zM325 936q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69q-17 -40 -59 -40 h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40zM1152 64q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM1408 64q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf094;" d="M0 433q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5q0 -165 -70 -327.5 t-196 -288t-281 -180.5q-124 -44 -326 -44q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5zM128 434q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5 q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24 q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5z" />
+<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M0 1069q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235 t235 -174q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5 t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5z" />
+<glyph unicode="&#xf096;" horiz-adv-x="1408" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM128 288q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47 t-47 -113v-832z" />
+<glyph unicode="&#xf097;" horiz-adv-x="1280" d="M0 7v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62zM128 38l423 406l89 85l89 -85l423 -406 v1242h-1024v-1242z" />
+<glyph unicode="&#xf098;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 905q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5 q6 -2 30 -11t33 -12.5t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5 t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5z" />
+<glyph unicode="&#xf099;" horiz-adv-x="1664" d="M44 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5 q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145z" />
+<glyph unicode="&#xf09a;" horiz-adv-x="1024" d="M95 631v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255z" />
+<glyph unicode="&#xf09b;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44 l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3 q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5z" />
+<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M0 96v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M0 32v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113zM128 32q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v608h-1664v-608zM128 1024h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600 q-13 0 -22.5 -9.5t-9.5 -22.5v-224zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M0 192q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM0 697v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5t259 -181.5q114 -113 181.5 -259t80.5 -306q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5 t-391.5 184.5q-25 2 -41.5 20t-16.5 43zM0 1201v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294q187 -186 294 -425.5t120 -501.5q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102 q-25 1 -42.5 19.5t-17.5 43.5z" />
+<glyph unicode="&#xf0a0;" d="M0 160v320q0 25 16 75l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113zM128 160q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5v320q0 13 -9.5 22.5t-22.5 9.5h-1216 q-13 0 -22.5 -9.5t-9.5 -22.5v-320zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM880 320q0 33 23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5t-23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5zM1136 320q0 33 23.5 56.5t56.5 23.5 t56.5 -23.5t23.5 -56.5t-23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5z" />
+<glyph unicode="&#xf0a1;" horiz-adv-x="1792" d="M0 672v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50 t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113zM768 633q377 -42 768 -341v954q-394 -302 -768 -343v-270z" />
+<glyph unicode="&#xf0a2;" horiz-adv-x="1664" d="M0 128q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38 t-38 90zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM656 0q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16z" />
+<glyph unicode="&#xf0a3;" d="M2 435q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70l-53 -186l188 -48 q40 -10 52 -51q10 -42 -20 -70l-138 -135l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53 q-41 -12 -70 19q-31 29 -19 70l53 186l-188 48q-40 10 -52 51z" />
+<glyph unicode="&#xf0a4;" horiz-adv-x="1792" d="M0 128v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179q0 -105 -75.5 -181 t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5zM128 192q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM384 128h32q72 0 167 -32 t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139 q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106q-69 -57 -140 -57h-32v-640z" />
+<glyph unicode="&#xf0a5;" horiz-adv-x="1792" d="M0 769q0 103 76 179t180 76h374q-22 60 -22 128q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5v-640 q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181zM128 768q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119 q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5 t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576q-50 0 -89 -38.5t-39 -89.5zM1536 192q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf0a6;" d="M0 640q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5t-90.5 -37.5h-640 q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5zM128 640q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140 v-32h640v32q0 72 32 167t64 193.5t32 179.5q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576q-20 0 -48.5 15t-55 33t-68 33t-84.5 15 q-67 0 -97.5 -44.5t-30.5 -115.5zM1152 -64q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf0a7;" d="M0 640q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317q0 -142 -77.5 -230t-217.5 -87 l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5zM128 640q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33t55 33t48.5 15v-576q0 -50 38.5 -89 t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112 q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5zM1152 1344q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf0a8;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM251 640q0 -27 18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502 q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45z" />
+<glyph unicode="&#xf0a9;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM256 576q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18 l362 362l91 91q18 18 18 45t-18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128z" />
+<glyph unicode="&#xf0aa;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM252 641q0 -27 18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19 t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45t-18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45z" />
+<glyph unicode="&#xf0ab;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM252 639q0 -27 18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45t-18 45l-91 91 q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45z" />
+<glyph unicode="&#xf0ac;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM226 979q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18 q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13 q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5 t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13 q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25 t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5 t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4 q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5q15 10 -7 16q-17 5 -43 -12q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8 q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5 q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26 q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5 q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14 q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5 q-16 0 -22 -1q-146 -80 -235 -222zM877 26q0 -6 2 -16q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7 t-10 1.5t-11.5 -7q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5z" />
+<glyph unicode="&#xf0ad;" horiz-adv-x="1664" d="M21 0q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90zM256 64q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45zM768 960q0 185 131.5 316.5t316.5 131.5q58 0 121.5 -16.5 t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25q0 -39 -23 -106q-47 -134 -164.5 -217.5t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5z" />
+<glyph unicode="&#xf0ae;" horiz-adv-x="1792" d="M0 64v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 576v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM0 1088v256q0 26 19 45t45 19h1664 q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45zM640 640h1024v128h-1024v-128zM1024 128h640v128h-640v-128zM1280 1152h384v128h-384v-128z" />
+<glyph unicode="&#xf0b0;" horiz-adv-x="1408" d="M5 1241q17 39 59 39h1280q42 0 59 -39q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70z" />
+<glyph unicode="&#xf0b1;" horiz-adv-x="1792" d="M0 160v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113zM0 736v384q0 66 47 113t113 47h352v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113v-384h-1792z M640 1280h512v128h-512v-128zM768 512v128h256v-128h-256z" />
+<glyph unicode="&#xf0b2;" d="M0 -64v448q0 42 40 59q39 17 69 -14l144 -144l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45 v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19l-144 144l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19 t-19 45z" />
+<glyph unicode="&#xf0c0;" horiz-adv-x="1920" d="M0 671q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5zM128 1280q0 106 75 181t181 75t181 -75t75 -181t-75 -181t-181 -75t-181 75t-75 181zM256 3q0 53 3.5 103.5 t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5 zM576 896q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5zM1280 1280q0 106 75 181t181 75t181 -75t75 -181t-75 -181t-181 -75t-181 75t-75 181zM1327 640q81 117 81 256q0 29 -5 66q66 -23 133 -23 q59 0 119 21.5t97.5 42.5t43.5 21q124 0 124 -353q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128z" />
+<glyph unicode="&#xf0c1;" horiz-adv-x="1664" d="M16 1088q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l206 -207q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204t-85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88 q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204zM208 1088q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15t21.5 -21.5t18.5 -19q33 31 33 73 q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67zM911 383q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26l147 146q28 28 28 67q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5 q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73z" />
+<glyph unicode="&#xf0c2;" horiz-adv-x="1920" d="M0 448q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5z" />
+<glyph unicode="&#xf0c3;" horiz-adv-x="1664" d="M115.5 -64.5q-34.5 63.5 21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399l503 -793q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5zM476 384h712l-272 429l-20 31v37v399h-128v-399v-37 l-20 -31z" />
+<glyph unicode="&#xf0c4;" horiz-adv-x="1792" d="M1 157q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148q4 -48 -10 -97q4 -1 12 -5 l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56l-507 -398l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207zM168 176q-25 -66 21 -108q39 -36 113 -36q100 0 192 59q81 51 106 117t-21 108q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117zM168 976q25 -66 106 -117q92 -59 192 -59q74 0 113 36q46 42 21 108t-106 117q-92 59 -192 59 q-74 0 -113 -36q-46 -42 -21 -108zM672 448l9 -8q2 -2 7 -6q4 -4 11 -12t11 -12l26 -26l160 96l96 -32l736 576l-128 64l-768 -431v-113zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 576q0 26 19 45t45 19t45 -19 t19 -45t-19 -45t-45 -19t-45 19t-19 45zM1018 391l582 -327l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="&#xf0c5;" horiz-adv-x="1792" d="M0 224v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68zM128 256h512v256q0 40 20 88t48 76l316 316v416h-384 v-416q0 -40 -28 -68t-68 -28h-416v-640zM213 1024h299v299zM768 -128h896v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640zM853 640h299v299z" />
+<glyph unicode="&#xf0c6;" horiz-adv-x="1408" d="M4 1023q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581 q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776 q-113 115 -113 271z" />
+<glyph unicode="&#xf0c7;" d="M0 -32v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 0h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20 t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280zM384 0h768v384h-768v-384zM640 928q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v320q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320z" />
+<glyph unicode="&#xf0c8;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5z" />
+<glyph unicode="&#xf0c9;" d="M0 64v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM0 576v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM0 1088v128q0 26 19 45t45 19h1408 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf0ca;" horiz-adv-x="1792" d="M0 128q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM0 640q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM0 1152q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM512 32v192 q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5zM512 544v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5z M512 1056v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0cb;" horiz-adv-x="1792" d="M15 438q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105v-159h-362q-6 36 -6 54zM19 -190 l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66zM34 1400l136 127h106v-404h108v-99 h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54zM512 32v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5zM512 544v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5v-192 q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5zM512 1056v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0cc;" horiz-adv-x="1792" d="M0 544v64q0 14 9 23t23 9h1728q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23zM384 972q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6l-14 2q-50 149 -103 205 q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743q-28 35 -51 80q-48 97 -48 188zM414 154q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22 q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156z" />
+<glyph unicode="&#xf0cd;" d="M0 -32v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-1472q-14 0 -23 -9t-9 -23zM0 1405q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5 t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2 q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195 q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39q-37 2 -45 4z" />
+<glyph unicode="&#xf0ce;" horiz-adv-x="1664" d="M0 160v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113zM128 160q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM128 544q0 -14 9 -23t23 -9h320 q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM128 928q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM640 160q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192zM640 544q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM640 928q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM1152 160q0 -14 9 -23t23 -9h320q14 0 23 9t9 23 v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM1152 544q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192zM1152 928q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192z" />
+<glyph unicode="&#xf0d0;" horiz-adv-x="1664" d="M27 160q0 27 18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45t-18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45zM128 1408l98 30l30 98l30 -98l98 -30l-98 -30l-30 -98l-30 98zM320 1216l196 60l60 196l60 -196l196 -60l-196 -60 l-60 -196l-60 196zM768 1408l98 30l30 98l30 -98l98 -30l-98 -30l-30 -98l-30 98zM1083 1062l107 -107l293 293l-107 107zM1408 768l98 30l30 98l30 -98l98 -30l-98 -30l-30 -98l-30 98z" />
+<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M64 192q0 26 19 45t45 19v320q0 8 -0.5 35t0 38t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45v-1024q0 -15 -4 -26.5t-13.5 -18.5t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5 q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM384 128q0 -52 38 -90t90 -38 t90 38t38 90t-38 90t-90 38t-90 -38t-38 -90zM1280 128q0 -52 38 -90t90 -38t90 38t38 90t-38 90t-90 38t-90 -38t-38 -90z" />
+<glyph unicode="&#xf0d2;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63 q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5 q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423z" />
+<glyph unicode="&#xf0d3;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5 q-104 0 -194.5 -28.5t-153 -76.5t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118 q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5z" />
+<glyph unicode="&#xf0d4;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM276 309q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117 q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94 q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95zM395 338q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78z M462 969q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5t-57.5 96.5t-17.5 106zM960 672h128v-160h64v160h128v64h-128v128h-64v-128h-128v-64z" />
+<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M32 182q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65 t-59.5 -61.5t-24.5 -66q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131zM218 228q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5q58 0 111.5 13t99 39t73 73 t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5zM324 1222q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q38 0 78 16.5t66 43.5q53 57 53 159 q0 58 -17 125t-48.5 129.5t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160zM1084 731v108h212v217h105v-217h213v-108h-213v-219h-105v219h-212z" />
+<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M0 64v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45zM128 384q106 0 181 -75t75 -181h1152q0 106 75 181t181 75v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512zM640 640q0 70 21 142 t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142t-21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142zM762 791l77 -80q42 37 55 57h2v-288h-128v-96h384v96h-128v448h-114z" />
+<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M0 832q0 26 19 45t45 19h896q26 0 45 -19t19 -45t-19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45z" />
+<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M0 320q0 26 19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf0d9;" horiz-adv-x="640" d="M64 640q0 26 19 45l448 448q19 19 45 19t45 -19t19 -45v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45z" />
+<glyph unicode="&#xf0da;" horiz-adv-x="640" d="M0 192v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45t-19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45z" />
+<glyph unicode="&#xf0db;" horiz-adv-x="1664" d="M0 32v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113zM128 32q0 -13 9.5 -22.5t22.5 -9.5h608v1152h-640v-1120zM896 0h608q13 0 22.5 9.5t9.5 22.5v1120h-640v-1152z" />
+<glyph unicode="&#xf0dc;" horiz-adv-x="1024" d="M0 448q0 26 19 45t45 19h896q26 0 45 -19t19 -45t-19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45zM0 832q0 26 19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf0dd;" horiz-adv-x="1024" d="M0 448q0 26 19 45t45 19h896q26 0 45 -19t19 -45t-19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45z" />
+<glyph unicode="&#xf0de;" horiz-adv-x="1024" d="M0 832q0 26 19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf0e0;" horiz-adv-x="1792" d="M0 32v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113zM0 1098q0 78 41.5 130t118.5 52h1472 q65 0 112.5 -47t47.5 -113q0 -79 -49 -151t-122 -123q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5z" />
+<glyph unicode="&#xf0e1;" d="M0 1217q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122zM19 -80v991h330v-991h-330zM531 -80q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5v-568 h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329z" />
+<glyph unicode="&#xf0e2;" d="M0 832v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298t-61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12 q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf0e3;" horiz-adv-x="1792" d="M40 736q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18 q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5 q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5 t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68z" />
+<glyph unicode="&#xf0e4;" horiz-adv-x="1792" d="M0 384q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29q-141 221 -141 483zM128 384q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z M320 832q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM710 241q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91l101 382q6 26 -7.5 48.5t-38.5 29.5t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5 t-63 -98.5zM768 1024q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM1216 832q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM1408 384q0 -53 37.5 -90.5 t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf0e5;" horiz-adv-x="1792" d="M0 640q0 174 120 321.5t326 233t450 85.5t450 -85.5t326 -233t120 -321.5t-120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5 t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281zM128 640q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5t-381.5 -69.5 t-282 -187.5t-104.5 -255z" />
+<glyph unicode="&#xf0e6;" horiz-adv-x="1792" d="M0 768q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257t-94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25 t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224zM128 768q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52t-286 -52t-211.5 -141t-78.5 -191zM616 132 q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22 t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132z" />
+<glyph unicode="&#xf0e7;" horiz-adv-x="896" d="M1 551l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39z" />
+<glyph unicode="&#xf0e8;" horiz-adv-x="1792" d="M0 -32v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf0e9;" horiz-adv-x="1664" d="M0 681q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17 q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5z M384 128q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180zM768 1310v98q0 26 19 45t45 19t45 -19t19 -45v-98q-42 2 -64 2t-64 -2z" />
+<glyph unicode="&#xf0ea;" horiz-adv-x="1792" d="M0 96v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88v-672q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68zM256 1312q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5v64 q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64zM768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1280 640h299l-299 299v-299z" />
+<glyph unicode="&#xf0eb;" horiz-adv-x="1024" d="M0 960q0 99 44.5 184.5t117 142t164 89t186.5 32.5t186.5 -32.5t164 -89t117 -142t44.5 -184.5q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47q0 -46 -31.5 -71t-77.5 -25 q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268zM128 960q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228 q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134zM480 1088q0 13 9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5 q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0ec;" horiz-adv-x="1792" d="M0 256q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22zM0 800v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23 t23 9q12 0 24 -10l319 -319q9 -9 9 -23t-9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0ed;" horiz-adv-x="1920" d="M0 448q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5z M512 608q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5z" />
+<glyph unicode="&#xf0ee;" horiz-adv-x="1920" d="M0 448q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5z M512 672q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24z" />
+<glyph unicode="&#xf0f0;" horiz-adv-x="1408" d="M0 131q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89 q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5 t81 -103t47.5 -132.5t24 -138t5.5 -131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190zM256 192q0 26 19 45t45 19t45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45zM320 1024q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5 t-271.5 112.5t-112.5 271.5z" />
+<glyph unicode="&#xf0f1;" horiz-adv-x="1408" d="M0 768v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48 q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5 t-131.5 271.5v132q-164 20 -274 128t-110 252zM1152 832q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf0f2;" horiz-adv-x="1792" d="M0 96v832q0 92 66 158t158 66h64v-1280h-64q-92 0 -158 66t-66 158zM384 -128v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128v-1280h-1024zM640 1152h512v128h-512v-128zM1504 -128v1280h64q92 0 158 -66t66 -158v-832q0 -92 -66 -158t-158 -66h-64z " />
+<glyph unicode="&#xf0f3;" horiz-adv-x="1664" d="M0 128q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38 t-38 90zM656 0q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16z" />
+<glyph unicode="&#xf0f4;" horiz-adv-x="1920" d="M0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM256 480v736q0 26 19 45t45 19h1152q159 0 271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158zM1408 704h64q80 0 136 56t56 136 t-56 136t-136 56h-64v-384z" />
+<glyph unicode="&#xf0f5;" horiz-adv-x="1408" d="M0 832v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128 q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111zM768 416v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0f6;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM384 160v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64 q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23zM384 416v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23zM384 672v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23zM1024 1024h376 q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf0f7;" horiz-adv-x="1408" d="M0 -192v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45zM128 -128h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224h384v1536h-1152v-1536zM256 160v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 672v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 928v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 1184v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 672v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 928v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 1184v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM768 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM768 672v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM768 928v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM768 1184v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 160v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 416v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 672v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 928v64q0 13 9.5 22.5t22.5 9.5h64 q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 1184v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0f8;" horiz-adv-x="1408" d="M0 -192v1280q0 26 19 45t45 19h320v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45zM128 -128h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224h384v1152h-256 v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152zM256 160v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5 v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM256 672v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64 q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 672v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM512 1056q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128 v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320zM768 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5 v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM768 672v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 160v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5 v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 416v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5zM1024 672v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5 v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf0f9;" horiz-adv-x="1920" d="M64 192q0 26 19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128 q-26 0 -45 19t-19 45zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM384 128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM896 800q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192 q14 0 23 9t9 23v224h224q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192zM1280 128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf0fa;" horiz-adv-x="1792" d="M0 96v832q0 92 66 158t158 66h32v-1280h-32q-92 0 -158 66t-66 158zM352 -128v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160v-1280h-1088zM512 416q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23v192 q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192zM640 1152h512v128h-512v-128zM1536 -128v1280h32q92 0 158 -66t66 -158v-832q0 -92 -66 -158t-158 -66h-32z" />
+<glyph unicode="&#xf0fb;" horiz-adv-x="1920" d="M0 512v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93l1 -3q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5 t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8z" />
+<glyph unicode="&#xf0fc;" horiz-adv-x="1664" d="M64 1152l32 128h480l32 128h960l32 -192l-64 -32v-800l128 -192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320zM384 768q0 -53 37.5 -90.5t90.5 -37.5h128v384h-256v-256z" />
+<glyph unicode="&#xf0fd;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 192q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45 v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896z" />
+<glyph unicode="&#xf0fe;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 576q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45 v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128z" />
+<glyph unicode="&#xf100;" horiz-adv-x="1024" d="M45 576q0 13 10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23zM429 576q0 13 10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23 l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23z" />
+<glyph unicode="&#xf101;" horiz-adv-x="1024" d="M13 160q0 13 10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23zM397 160q0 13 10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10 l466 -466q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23z" />
+<glyph unicode="&#xf102;" horiz-adv-x="1152" d="M77 224q0 13 10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23zM77 608q0 13 10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23 l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23z" />
+<glyph unicode="&#xf103;" horiz-adv-x="1152" d="M77 672q0 13 10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23zM77 1056q0 13 10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10 l50 -50q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23z" />
+<glyph unicode="&#xf104;" horiz-adv-x="640" d="M45 576q0 13 10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23z" />
+<glyph unicode="&#xf105;" horiz-adv-x="640" d="M13 160q0 13 10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23z" />
+<glyph unicode="&#xf106;" horiz-adv-x="1152" d="M77 352q0 13 10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23z" />
+<glyph unicode="&#xf107;" horiz-adv-x="1152" d="M77 800q0 13 10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23z" />
+<glyph unicode="&#xf108;" horiz-adv-x="1920" d="M0 288v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113zM128 544q0 -13 9.5 -22.5 t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832z" />
+<glyph unicode="&#xf109;" horiz-adv-x="1920" d="M0 96v96h160h1600h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68zM256 416v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088q-66 0 -113 47t-47 113zM384 416q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5 t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-704zM864 112q0 -16 16 -16h160q16 0 16 16t-16 16h-160q-16 0 -16 -16z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="1152" d="M0 160v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-832q-66 0 -113 47t-47 113zM128 288q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960zM512 128 q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="768" d="M0 128v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90zM96 288q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704zM288 1136 q0 -16 16 -16h160q16 0 16 16t-16 16h-160q-16 0 -16 -16zM304 128q0 -33 23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5t-23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5z" />
+<glyph unicode="&#xf10c;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M0 192v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136z M896 192v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136z" />
+<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M0 832v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136zM896 832v384 q0 80 56 136t136 56h384q80 0 136 -56t56 -136v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136z" />
+<glyph unicode="&#xf110;" horiz-adv-x="1568" d="M0 640q0 66 47 113t113 47t113 -47t47 -113t-47 -113t-113 -47t-113 47t-47 113zM176 1088q0 73 51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5t-51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5zM208 192q0 60 42 102t102 42q59 0 101.5 -42t42.5 -102t-42.5 -102 t-101.5 -42q-60 0 -102 42t-42 102zM608 1280q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM672 0q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM1136 192q0 46 33 79t79 33t79 -33t33 -79 t-33 -79t-79 -33t-79 33t-33 79zM1168 1088q0 33 23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5t-23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5zM1344 640q0 40 28 68t68 28t68 -28t28 -68t-28 -68t-68 -28t-68 28t-28 68z" />
+<glyph unicode="&#xf111;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M0 896q0 26 19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101 t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19l-512 512q-19 19 -19 45z" />
+<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M0 496q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218q0 -87 -27 -168q136 -160 136 -398q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86t-170 -47.5t-171.5 -22t-167 -4.5 q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331zM224 320q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11 q-152 21 -195 21q-118 0 -187 -84t-69 -204zM384 320q0 40 12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82t-12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82zM1024 320q0 40 12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82t-12.5 -82t-43 -76t-72.5 -34t-72.5 34 t-43 76t-12.5 82z" />
+<glyph unicode="&#xf114;" horiz-adv-x="1664" d="M0 224v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158zM128 224q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64 q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960z" />
+<glyph unicode="&#xf115;" horiz-adv-x="1920" d="M0 224v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158zM128 331l256 315q44 53 116 87.5 t140 34.5h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-853zM171 163q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40z " />
+<glyph unicode="&#xf116;" horiz-adv-x="1792" />
+<glyph unicode="&#xf117;" horiz-adv-x="1792" />
+<glyph unicode="&#xf118;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5zM384 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM402 461q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38 q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5q-37 -121 -138 -195t-228 -74t-228 74t-138 195zM896 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5z" />
+<glyph unicode="&#xf119;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5zM384 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM402 307q37 121 138 195t228 74t228 -74t138 -195q8 -25 -4 -48.5 t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5zM896 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5z" />
+<glyph unicode="&#xf11a;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5zM384 448q0 26 19 45t45 19h640q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45zM384 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5 t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM896 896q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5z" />
+<glyph unicode="&#xf11b;" horiz-adv-x="1920" d="M0 512q0 212 150 362t362 150h896q212 0 362 -150t150 -362t-150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150t-150 362zM192 448q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23v128 q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128zM1152 384q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM1408 640q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z" />
+<glyph unicode="&#xf11c;" horiz-adv-x="1920" d="M0 128v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5zM128 128h1664v896h-1664v-896zM256 272v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM256 528v96 q0 16 16 16h224q16 0 16 -16v-96q0 -16 -16 -16h-224q-16 0 -16 16zM256 784v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM512 272v96q0 16 16 16h864q16 0 16 -16v-96q0 -16 -16 -16h-864q-16 0 -16 16zM512 784v96q0 16 16 16h96q16 0 16 -16v-96 q0 -16 -16 -16h-96q-16 0 -16 16zM640 528v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM768 784v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM896 528v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16z M1024 784v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM1152 528v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM1280 784v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16zM1408 528v96q0 16 16 16h112v240 q0 16 16 16h96q16 0 16 -16v-352q0 -16 -16 -16h-224q-16 0 -16 16zM1536 272v96q0 16 16 16h96q16 0 16 -16v-96q0 -16 -16 -16h-96q-16 0 -16 16z" />
+<glyph unicode="&#xf11d;" horiz-adv-x="1792" d="M64 1280q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64zM320 320v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86 q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56zM448 426 q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599z" />
+<glyph unicode="&#xf11e;" horiz-adv-x="1792" d="M64 1280q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64zM320 320v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86 q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56zM448 426 q205 96 384 110v192q-181 -16 -384 -117v-185zM448 836q215 111 384 118v197q-172 -8 -384 -126v-189zM832 730h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15 t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2q-23 0 -49 -3v-222zM1280 828q148 -42 384 90v189q-169 -91 -306 -91q-45 0 -78 8v-196z" />
+<glyph unicode="&#xf120;" horiz-adv-x="1664" d="M13 160q0 13 10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23zM640 32v64q0 14 9 23t23 9h960q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-960 q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf121;" horiz-adv-x="1920" d="M45 576q0 13 10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23zM712 -52l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5 l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5zM1293 160q0 13 10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23z" />
+<glyph unicode="&#xf122;" horiz-adv-x="1792" d="M0 896q0 26 19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45l397 -397v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45zM384 896q0 26 19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221 q169 -173 169 -509q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45z" />
+<glyph unicode="&#xf123;" horiz-adv-x="1664" d="M2 900.5q9 27.5 54 34.5l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5z M832 310l59 -31l318 -168l-60 355l-12 66l49 47l257 250l-356 52l-66 10l-30 60l-159 322v-963z" />
+<glyph unicode="&#xf124;" horiz-adv-x="1408" d="M2 561q-5 22 4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5z" />
+<glyph unicode="&#xf125;" horiz-adv-x="1664" d="M0 928v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864 q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23zM512 301l595 595h-595v-595zM557 256h595v595z" />
+<glyph unicode="&#xf126;" horiz-adv-x="1024" d="M0 64q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136 q0 -52 -26 -96.5t-70 -69.5q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136zM96 64q0 -40 28 -68t68 -28t68 28t28 68t-28 68t-68 28t-68 -28t-28 -68zM96 1216q0 -40 28 -68 t68 -28t68 28t28 68t-28 68t-68 28t-68 -28t-28 -68zM736 1088q0 -40 28 -68t68 -28t68 28t28 68t-28 68t-68 28t-68 -28t-28 -68z" />
+<glyph unicode="&#xf127;" horiz-adv-x="1664" d="M0 448q0 14 9 23t23 9h320q14 0 23 -9t9 -23t-9 -23t-23 -9h-320q-14 0 -23 9t-9 23zM16 1088q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56l-239 -18l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68 l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204zM128 32q0 13 9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23zM544 -96v320q0 14 9 23t23 9t23 -9t9 -23v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23zM633 364 l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56zM1056 1184v320q0 14 9 23t23 9t23 -9t9 -23v-320 q0 -14 -9 -23t-23 -9t-23 9t-9 23zM1216 1120q0 13 9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23zM1280 960q0 14 9 23t23 9h320q14 0 23 -9t9 -23t-9 -23t-23 -9h-320q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf128;" horiz-adv-x="1024" d="M96.5 986q-2.5 15 5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5t-10.5 37.5v45q0 83 65 156.5 t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25zM384 40v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28z" />
+<glyph unicode="&#xf129;" horiz-adv-x="640" d="M0 64v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45zM128 1152v192q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-192 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf12a;" horiz-adv-x="640" d="M98 1344q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45zM128 64v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf12b;" d="M5 0v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258zM1013 713q0 64 26 117t65 86.5 t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80h126v-206h-514l-3 27q-4 28 -4 46z " />
+<glyph unicode="&#xf12c;" d="M5 0v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258zM1015 -183q0 64 26 117t65 86.5 t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126v-206h-514l-4 27q-3 45 -3 46z" />
+<glyph unicode="&#xf12d;" horiz-adv-x="1920" d="M1.5 146.5q5.5 37.5 30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5zM128 128h768l336 384h-768z" />
+<glyph unicode="&#xf12e;" horiz-adv-x="1664" d="M0 0v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5 q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124 q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89 q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1z" />
+<glyph unicode="&#xf130;" horiz-adv-x="1152" d="M0 704v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45 t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5zM256 704v512q0 132 94 226t226 94t226 -94t94 -226v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226z" />
+<glyph unicode="&#xf131;" horiz-adv-x="1408" d="M13 64q0 13 10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23t-10 -23l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -221 -147.5 -384.5 t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23zM128 704v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113l-101 -101 q-42 103 -42 214zM384 704v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
+<glyph unicode="&#xf132;" horiz-adv-x="1280" d="M0 576v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150t-33.5 170.5zM640 79 q119 63 213 137q235 184 235 360v640h-448v-1137z" />
+<glyph unicode="&#xf133;" horiz-adv-x="1664" d="M0 -128v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90zM128 -128h1408v1024h-1408v-1024z M384 1088q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288zM1152 1088q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288z" />
+<glyph unicode="&#xf134;" horiz-adv-x="1408" d="M3.5 940q-8.5 25 3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96 q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37 zM384 1344q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf135;" horiz-adv-x="1664" d="M36 464l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85 q-3 -1 -9 -1q-14 0 -23 9l-64 64q-17 19 -5 39zM1248 1088q0 -40 28 -68t68 -28t68 28t28 68t-28 68t-68 28t-68 -28t-28 -68z" />
+<glyph unicode="&#xf136;" horiz-adv-x="1792" d="M0 0l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334z" />
+<glyph unicode="&#xf137;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM346 640q0 -26 19 -45l454 -454q19 -19 45 -19t45 19l102 102q19 19 19 45t-19 45l-307 307l307 307 q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45z" />
+<glyph unicode="&#xf138;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM506 288q0 -26 19 -45l102 -102q19 -19 45 -19t45 19l454 454q19 19 19 45t-19 45l-454 454 q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45z" />
+<glyph unicode="&#xf139;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM250 544q0 -26 19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19l102 102 q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45z" />
+<glyph unicode="&#xf13a;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM250 736q0 -26 19 -45l454 -454q19 -19 45 -19t45 19l454 454q19 19 19 45t-19 45l-102 102 q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45z" />
+<glyph unicode="&#xf13b;" horiz-adv-x="1408" d="M0 1408h1408l-128 -1438l-578 -162l-574 162zM262 1114l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674l16 175h-884z" />
+<glyph unicode="&#xf13c;" horiz-adv-x="1792" d="M12 75l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208l59 297h1505l-266 -1333l-804 -267z" />
+<glyph unicode="&#xf13d;" horiz-adv-x="1792" d="M0 0v352q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192 q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5 l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30zM832 1280q0 -26 19 -45t45 -19t45 19t19 45t-19 45t-45 19t-45 -19t-19 -45z" />
+<glyph unicode="&#xf13e;" horiz-adv-x="1152" d="M0 96v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181v-320h736q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28 t-28 68z" />
+<glyph unicode="&#xf140;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5zM256 640q0 212 150 362t362 150t362 -150t150 -362t-150 -362t-362 -150t-362 150t-150 362zM384 640q0 -159 112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5t-112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5zM512 640q0 106 75 181t181 75t181 -75t75 -181t-75 -181t-181 -75t-181 75t-75 181z" />
+<glyph unicode="&#xf141;" horiz-adv-x="1408" d="M0 608v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68zM512 608v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68zM1024 608v192q0 40 28 68t68 28h192 q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf142;" horiz-adv-x="384" d="M0 96v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68zM0 608v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68zM0 1120v192q0 40 28 68t68 28h192q40 0 68 -28 t28 -68v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68z" />
+<glyph unicode="&#xf143;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 256q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5z M256 575q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10t9 24q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128zM256 959q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128 q13 0 23 10q11 9 9 23q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128z" />
+<glyph unicode="&#xf144;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM512 320q0 -37 32 -56q16 -8 32 -8q17 0 32 9l544 320q32 18 32 55t-32 55l-544 320q-31 19 -64 1 q-32 -19 -32 -56v-640z" />
+<glyph unicode="&#xf145;" horiz-adv-x="1792" d="M54 448.5q0 53.5 37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136t-136 56t-136 -56l-125 126q-37 37 -37 90.5z M342 512q0 -26 19 -45l362 -362q18 -18 45 -18t45 18l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45zM452 512l572 572l316 -316l-572 -572z" />
+<glyph unicode="&#xf146;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 576q0 -26 19 -45t45 -19h896q26 0 45 19t19 45v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128 z" />
+<glyph unicode="&#xf147;" horiz-adv-x="1408" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM128 288q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47 t-47 -113v-832zM256 672v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf148;" horiz-adv-x="1024" d="M3 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18z" />
+<glyph unicode="&#xf149;" horiz-adv-x="1024" d="M3 1261q9 19 29 19h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34z" />
+<glyph unicode="&#xf14a;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM218 640q0 -26 19 -45l358 -358q19 -19 45 -19t45 19l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19 t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45z" />
+<glyph unicode="&#xf14b;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 128h288l544 544l-288 288l-544 -544v-288zM352 320v56l52 52l152 -152l-52 -52h-56v96h-96zM494 494 q-14 13 3 30l291 291q17 17 30 3q14 -13 -3 -30l-291 -291q-17 -17 -30 -3zM864 1024l288 -288l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28z" />
+<glyph unicode="&#xf14c;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM282 320q0 -26 19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59 v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45z" />
+<glyph unicode="&#xf14d;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 448q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5t224 23.5v-160 q0 -42 40 -59q12 -5 24 -5q26 0 45 19l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5z" />
+<glyph unicode="&#xf14e;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM512 241v542l512 256v-542zM640 448l256 128l-256 128v-256z" />
+<glyph unicode="&#xf150;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 160q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5v960q0 13 -9.5 22.5t-22.5 9.5h-960 q-13 0 -22.5 -9.5t-9.5 -22.5v-960zM391 861q17 35 57 35h640q40 0 57 -35q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66z" />
+<glyph unicode="&#xf151;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 160q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5v960q0 13 -9.5 22.5t-22.5 9.5h-960 q-13 0 -22.5 -9.5t-9.5 -22.5v-960zM391 419q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66q-17 -35 -57 -35h-640q-40 0 -57 35z" />
+<glyph unicode="&#xf152;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 160q0 -14 9 -23t23 -9h960q14 0 23 9t9 23v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960z M512 320v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52t-27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57z" />
+<glyph unicode="&#xf153;" horiz-adv-x="1024" d="M0 514v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5 t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5 l12 3l5 2q13 5 26 -2q12 -7 15 -21l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf154;" horiz-adv-x="1024" d="M0 32v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215 h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf155;" horiz-adv-x="1024" d="M52 171l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242 t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48 t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50t53 -63.5t31.5 -76.5t13 -94q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5 t-17.5 18q-17 21 -2 41z" />
+<glyph unicode="&#xf156;" horiz-adv-x="898" d="M0 605v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171 q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22z" />
+<glyph unicode="&#xf157;" horiz-adv-x="1027" d="M4 1360q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103 q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214z" />
+<glyph unicode="&#xf158;" horiz-adv-x="1280" d="M0 256v128q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315t-126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9 h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23zM487 747h320q106 0 171 62t65 162t-65 162t-171 62h-320v-448z" />
+<glyph unicode="&#xf159;" horiz-adv-x="1792" d="M0 672v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111 q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23z M373 896l32 -128h225l35 128h-292zM436 640l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5l81 299h-159zM822 768h139l-35 128h-70zM1118 896l34 -128h230l33 128h-297zM1187 640l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3l78 300h-162z" />
+<glyph unicode="&#xf15a;" horiz-adv-x="1280" d="M56 0l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89 t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200zM522 182q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30t24.5 40t9.5 51q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1 t-47.5 -1v-338zM522 674q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307z" />
+<glyph unicode="&#xf15b;" d="M0 -160v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472z" />
+<glyph unicode="&#xf15c;" d="M0 -160v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM384 160q0 -14 9 -23t23 -9h704q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64zM384 416q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64zM384 672q0 -14 9 -23t23 -9h704q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64zM1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472z" />
+<glyph unicode="&#xf15d;" horiz-adv-x="1664" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM899 768v106h70l230 662h162l230 -662h70v-106h-288v106h75l-47 144h-243l-47 -144h75v-106 h-287zM988 -166l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121v-233h-584v90zM1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18 t-7.5 -29z" />
+<glyph unicode="&#xf15e;" horiz-adv-x="1664" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM899 -150h70l230 662h162l230 -662h70v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287 v106zM988 768v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248v119h121v-233h-584zM1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29 z" />
+<glyph unicode="&#xf160;" horiz-adv-x="1792" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM896 -32q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9 t-9 23v192zM896 288v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23zM896 800v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23zM896 1312v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23 v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf161;" horiz-adv-x="1792" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM896 -32q0 14 9 23t23 9h256q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9 t-9 23v192zM896 288v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23zM896 800v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23zM896 1312v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23 v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf162;" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM946 261q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5q0 -62 -13 -121.5t-41 -114 t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5zM976 1351l192 185h123v-654h165v-114h-469v114h167v432q0 7 0.5 19t0.5 17 v16h-2l-7 -12q-8 -13 -26 -31l-62 -58zM1085 261q0 -57 36.5 -95t104.5 -38q50 0 85 27t35 68q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94z" />
+<glyph unicode="&#xf163;" d="M34 108q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35zM946 1285q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5q0 -62 -13 -121.5t-41 -114 t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5zM976 327l192 185h123v-654h165v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16 h-2l-7 -12q-8 -13 -26 -31l-62 -58zM1085 1285q0 -57 36.5 -95t104.5 -38q50 0 85 27t35 68q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94z" />
+<glyph unicode="&#xf164;" horiz-adv-x="1664" d="M0 64v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45zM128 192q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45zM480 64v641q0 25 18 43.5t43 20.5q24 2 76 59 t101 121q68 87 101 120q18 18 31 48t17.5 48.5t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135 q0 -86 -55 -149q15 -44 15 -76q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5z" />
+<glyph unicode="&#xf165;" horiz-adv-x="1664" d="M0 448q0 -26 19 -45t45 -19h288q26 0 45 19t19 45v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640zM128 960q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45zM480 447v641q0 26 19 44.5t45 19.5q35 1 158 44 q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76q55 -61 55 -149q-1 -78 -57.5 -135t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39 t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121t-76 59q-25 2 -43 20.5t-18 43.5z" />
+<glyph unicode="&#xf166;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM218 366q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73 q20 84 20 260q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5q-20 -87 -20 -260zM300 551v70h232v-70h-80v-423h-74v423h-78zM396 1313l24 -69t23 -69q35 -103 46 -158v-201h74v201l90 296h-75l-51 -195l-53 195 h-78zM542 205v290h66v-270q0 -24 1 -26q1 -15 15 -15q20 0 42 31v280h67v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54zM654 936q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38q-21 -29 -21 -87v-130zM721 923 v156q0 52 32 52t32 -52v-156q0 -51 -32 -51t-32 51zM790 128v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67zM857 200q16 -16 33 -16q29 0 29 49v157q0 50 -29 50q-17 0 -33 -16v-224zM907 893q0 -37 6 -55 q11 -27 43 -27q36 0 77 45v-40h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293zM1037 247v129q0 59 20 86q29 38 80 38t78 -38q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68v-9q0 -29 -2 -43q-3 -22 -15 -40 q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86zM1103 355h66v34q0 51 -33 51t-33 -51v-34z" />
+<glyph unicode="&#xf167;" d="M27 260q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99q-26 112 -26 350zM138 509h105v-569h100v569h107v94h-312 v-94zM266 1536h106l71 -263l68 263h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187zM463 43q0 -49 8 -73q12 -37 58 -37q48 0 102 61v-54h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391zM614 1028v175q0 80 28 117q38 51 105 51 q69 0 106 -51q28 -37 28 -117v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51q-28 38 -28 118zM704 1011q0 -70 43 -70t43 70v210q0 69 -43 69t-43 -69v-210zM798 -60h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89 v-663zM887 36v301q22 22 45 22q39 0 39 -67v-211q0 -67 -39 -67q-23 0 -45 22zM955 971v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75zM1130 100q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54 q2 9 2 58v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51q-28 -37 -28 -116v-173zM1219 245v46q0 68 45 68t45 -68v-46h-90z" />
+<glyph unicode="&#xf168;" horiz-adv-x="1408" d="M5 384q-10 17 0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45l164 -286q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17zM536 539q18 32 531 942q25 45 64 45h241q22 0 31 -15q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37 q-10 -15 -32 -15h-239q-42 0 -66 45z" />
+<glyph unicode="&#xf169;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM227 396q8 -13 24 -13h185q31 0 50 36l199 352q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29 l125 -216v-1l-196 -346q-9 -14 0 -28zM638 516q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1l409 723q8 16 0 28q-7 12 -24 12h-187q-30 0 -49 -35z" />
+<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M0 640q0 96 1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150t-1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58 t-69.5 123q-14 65 -21.5 147.5t-8.5 136.5t-1 150zM640 320q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54t-30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640z" />
+<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M64 558l338 271l494 -305l-342 -285zM64 1099l490 319l342 -285l-494 -304zM407 166v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284l147 96v-108l-490 -293v-1l-1 1l-1 -1v1zM896 524l494 305l338 -271l-489 -319zM896 1133l343 285l489 -319l-338 -270z" />
+<glyph unicode="&#xf16c;" horiz-adv-x="1408" d="M0 -255v736h121v-618h928v618h120v-701l-1 -35v-1h-1132l-35 1h-1zM221 -17v151l707 1v-151zM227 243l14 150l704 -65l-13 -150zM270 563l39 146l683 -183l-39 -146zM395 928l77 130l609 -360l-77 -130zM707 1303l125 86l398 -585l-124 -85zM1136 1510l149 26l121 -697 l-149 -26z" />
+<glyph unicode="&#xf16d;" d="M0 69v1142q0 81 58 139t139 58h1142q81 0 139 -58t58 -139v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139zM171 110q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62 q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648zM461 643q0 -124 90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5t-90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5zM1050 1003q0 -29 20 -49t49 -20h174q29 0 49 20t20 49v165q0 28 -20 48.5 t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165z" />
+<glyph unicode="&#xf16e;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM274 640q0 -88 62 -150t150 -62t150 62t62 150t-62 150t-150 62t-150 -62t-62 -150zM838 640q0 -88 62 -150 t150 -62t150 62t62 150t-62 150t-150 62t-150 -62t-62 -150z" />
+<glyph unicode="&#xf170;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM309 384h94l104 160h522l104 -160h94l-459 691zM567 608l201 306l201 -306h-402z" />
+<glyph unicode="&#xf171;" horiz-adv-x="1408" d="M0 1222q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34 t-6 39.5t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158zM173 285l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18t-76.5 27 t-73 43.5t-52 61.5q-25 96 -57 292zM243 1240q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5q-20 27 -56 44.5t-58 22t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43zM481 657q4 -91 77.5 -155t165.5 -56q91 8 152 84 t50 168q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5zM599 710q14 41 52 58q36 18 72.5 12t64 -35.5t27.5 -67.5q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82z" />
+<glyph unicode="&#xf172;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM260 1060q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63 q24 13 39.5 23t31 29t19.5 40q48 267 80 473q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54zM385 384q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71q0 7 5.5 26.5t3 32 t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6zM436 1073q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5t-52.5 16t-54.5 32.5zM607 653q-2 49 25.5 93t72.5 64 q70 31 141.5 -10t81.5 -118q8 -66 -36 -121t-110 -61t-119 40t-56 113zM687.5 660.5q0.5 -52.5 43.5 -70.5q39 -23 81 4t36 72q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5z" />
+<glyph unicode="&#xf173;" horiz-adv-x="1024" d="M78 779v217q91 30 155 84q64 55 103 132q39 78 54 196h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170z" />
+<glyph unicode="&#xf174;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM413 744h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49v181 q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163z" />
+<glyph unicode="&#xf175;" horiz-adv-x="768" d="M3 237q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19t-5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35z" />
+<glyph unicode="&#xf176;" horiz-adv-x="768" d="M3 1043q-8 19 5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19z" />
+<glyph unicode="&#xf177;" horiz-adv-x="1792" d="M64 637q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23z" />
+<glyph unicode="&#xf178;" horiz-adv-x="1792" d="M0 544v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf179;" horiz-adv-x="1408" d="M0 634q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32 q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503zM683 1131q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17z" />
+<glyph unicode="&#xf17a;" horiz-adv-x="1664" d="M0 -27v557h682v-651zM0 614v565l682 94v-659h-682zM757 -131v661h907v-786zM757 614v669l907 125v-794h-907z" />
+<glyph unicode="&#xf17b;" horiz-adv-x="1408" d="M0 337v430q0 42 30 72t73 30q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30t-73 30t-30 73zM241 886q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20l-71 -131q107 -55 171 -153.5t64 -215.5 h-925zM245 184v666h918v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78zM455 1092q0 -16 11 -27.5t27 -11.5t27.5 11.5t11.5 27.5t-11.5 27.5 t-27.5 11.5t-27 -11.5t-11 -27.5zM876 1092q0 -16 11.5 -27.5t27.5 -11.5t27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5zM1203 337v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73z" />
+<glyph unicode="&#xf17c;" d="M11 -115q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48q3 -17 37 -26q20 -6 84.5 -18.5 t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195q-12 112 -16 310q-2 90 24 151.5 t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5 t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13 t16.5 -9.5q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5 q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5zM321 495q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54 t7 -70.5q46 24 7 92q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5 t60 -22.5q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18zM372 630q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12zM603 1190q2 -5 5 -6 q10 0 7 -15q-3 -20 8 -20q3 0 3 3q3 17 -2.5 30t-11.5 15q-9 2 -9 -7zM634 1110q0 12 19 15h10q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5zM721 1122q24 11 32 -2q3 -6 -3 -9q-4 -1 -11.5 6.5t-17.5 4.5zM835 1196l4 -2q14 -4 18 -31q0 -3 8 2l2 3q0 11 -5 19.5t-11 12.5 t-9 3q-14 -1 -7 -7zM851 1381.5q-1 -2.5 3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9zM981 1002q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20q-2 8 -6.5 11.5t-13 5 t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5z" />
+<glyph unicode="&#xf17d;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM112 640q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81t99.5 48l37 13 q4 1 13 3.5t13 4.5q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21zM126 775q302 0 606 80q-120 213 -244 378q-138 -65 -234 -186t-128 -272zM350 134q184 -150 418 -150q132 0 256 52q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5 t-103 -148zM609 1276q1 1 2 1q-1 0 -2 -1zM613 1277q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5l12.5 17.5q-185 164 -433 164q-76 0 -155 -19zM909 797q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5 t36.5 -6t25 -4.5l10 -2q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5zM1007 565q87 -239 128 -469q111 75 185 189.5t96 250.5q-210 60 -409 29z" />
+<glyph unicode="&#xf17e;" d="M0 1024q0 159 112.5 271.5t271.5 112.5q130 0 234 -80q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225 t-55.5 273.5q0 73 16 150q-80 104 -80 234zM376 399q0 -92 122 -157.5t291 -65.5q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12 t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5 q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75z" />
+<glyph unicode="&#xf180;" horiz-adv-x="1664" d="M0 640q0 75 53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41l619 619l2 -3q53 -53 53 -128t-53 -128l-587 -587 q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128zM302 660q0 21 14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36z" />
+<glyph unicode="&#xf181;" d="M0 -64v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM160 192q0 -14 9 -23t23 -9h480q14 0 23 9t9 23v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024zM832 576q0 -14 9 -23t23 -9h480q14 0 23 9t9 23 v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640z" />
+<glyph unicode="&#xf182;" horiz-adv-x="1280" d="M0 480q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192 q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43q-40 0 -68 28t-28 68zM416 1280q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5t-65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5z" />
+<glyph unicode="&#xf183;" horiz-adv-x="1024" d="M0 416v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68z M288 1280q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5t-65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5z" />
+<glyph unicode="&#xf184;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM399.5 766q8.5 -37 24.5 -59l349 -473l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5 t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85z" />
+<glyph unicode="&#xf185;" horiz-adv-x="1792" d="M44 363q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29q-4 -15 -20 -20 l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20zM320 640q0 -117 45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5 t-45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5z" />
+<glyph unicode="&#xf186;" d="M0 640q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384z" />
+<glyph unicode="&#xf187;" horiz-adv-x="1792" d="M64 1088v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1536q-26 0 -45 19t-19 45zM128 -64v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45zM704 704q0 -26 19 -45t45 -19h256 q26 0 45 19t19 45t-19 45t-45 19h-256q-26 0 -45 -19t-19 -45z" />
+<glyph unicode="&#xf188;" horiz-adv-x="1664" d="M32 576q0 26 19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19t19 -45t-19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19 t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45z M512 1152q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5h-640z" />
+<glyph unicode="&#xf189;" horiz-adv-x="1920" d="M-1 1004q0 11 3 16l4 6q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24 q17 19 38 30q53 26 239 24q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5 t13 3t20 0.5l288 2q39 5 64 -2.5t31 -16.5l6 -10q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12 q-30 21 -70 64t-68.5 77.5t-61 58t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211 t-130.5 272q-6 16 -6 27z" />
+<glyph unicode="&#xf18a;" horiz-adv-x="1792" d="M0 391q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5q0 -68 -37 -139.5 t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5zM181 320q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5zM413.5 230.5 q-40.5 92.5 6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5q-45 -102 -158 -150t-224 -12q-107 34 -147.5 126.5zM495 257.5q9 -34.5 43 -50.5t74.5 -2.5t62.5 47.5q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5zM705 399 q-17 -31 13 -45q14 -5 29 0.5t22 18.5q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5zM1165 1274q-6 28 9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158 q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5zM1224 1047q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5t54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37z" />
+<glyph unicode="&#xf18b;" d="M0 638q0 187 83.5 349.5t229.5 269.5t325 137v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495zM398 -34q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211q-171 -94 -368 -94q-196 0 -367 94zM898 909v485q179 -30 325 -137t229.5 -269.5 t83.5 -349.5q0 -280 -181 -495q-204 99 -330.5 306.5t-126.5 459.5z" />
+<glyph unicode="&#xf18c;" horiz-adv-x="1408" d="M0 -211q0 19 13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23 t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89 t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283 q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32z" />
+<glyph unicode="&#xf18d;" horiz-adv-x="1280" d="M21 217v66h1238v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5zM21 354v255h1238v-255h-1238zM21 682v255h1238v-255h-1238zM21 1010v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5v-67h-1238z" />
+<glyph unicode="&#xf18e;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM384 544v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23t-9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5z" />
+<glyph unicode="&#xf190;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM384 640q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23z" />
+<glyph unicode="&#xf191;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 160q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5v960q0 13 -9.5 22.5t-22.5 9.5h-960 q-13 0 -22.5 -9.5t-9.5 -22.5v-960zM448 640q0 33 27 52l448 320q17 12 37 12q26 0 45 -19t19 -45v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52z" />
+<glyph unicode="&#xf192;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM224 640q0 -148 73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73 t-198 -198t-73 -273zM512 640q0 106 75 181t181 75t181 -75t75 -181t-75 -181t-181 -75t-181 75t-75 181z" />
+<glyph unicode="&#xf193;" horiz-adv-x="1664" d="M0 320q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5zM416 1348q-2 16 6 42 q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455l198 99l58 -114l-256 -128q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5z" />
+<glyph unicode="&#xf194;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM128 806q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5 q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237q104 139 172.5 292.5t82.5 226.5q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143 q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7z" />
+<glyph unicode="&#xf195;" horiz-adv-x="1152" d="M0 608v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31 l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26z" />
+<glyph unicode="&#xf196;" horiz-adv-x="1408" d="M0 288v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5zM128 288q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47 t-47 -113v-832zM256 672v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf197;" horiz-adv-x="2176" d="M0 576q0 12 38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113q-110 -64 -268 -64h-128v64 h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5zM323 336h29q157 0 273 64h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96zM323 816l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5 t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64h-29zM1739 484l81 -30q68 48 68 122t-68 122l-81 -30q53 -36 53 -92t-53 -92z" />
+<glyph unicode="&#xf198;" horiz-adv-x="1664" d="M0 796q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5 t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72 l-55 163l-153 -53q-29 -9 -50 -9q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5zM620 811l105 -313l310 105l-105 315z" />
+<glyph unicode="&#xf199;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 352q0 -40 28 -68t68 -28h832q40 0 68 28t28 68v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99 q-98 -69 -164 -69t-164 69q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436zM256 928q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13 t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68z" />
+<glyph unicode="&#xf19a;" horiz-adv-x="1792" d="M0 640q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348zM41 640q0 -173 68 -331.5t182.5 -273t273 -182.5t331.5 -68t331.5 68t273 182.5t182.5 273t68 331.5 t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5zM127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM254 1062q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5 q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21t14.5 -24t14 -23q63 -107 63 -212q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15 t-1.5 -18.5t9 -16.5t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5zM679 -97l230 670l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1282 -24l235 678q59 169 59 276q0 42 -6 79 q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5z" />
+<glyph unicode="&#xf19b;" horiz-adv-x="1792" d="M0 455q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360l272 133v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5zM1134 860v172q277 -33 481 -157l140 79l37 -390l-525 114l147 83 q-119 70 -280 99z" />
+<glyph unicode="&#xf19c;" horiz-adv-x="2048" d="M0 -128q0 26 20.5 45t48.5 19h1782q28 0 48.5 -19t20.5 -45v-128h-1920v128zM0 1024v128l960 384l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128zM128 0v64q0 26 20.5 45t48.5 19h59v768h256v-768h128v768h256v-768h128v768h256 v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664z" />
+<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M0 1024q0 23 22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31t-22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433 q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31zM512 384l18 316l574 -181q22 -7 48 -7t48 7l574 181l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128z" />
+<glyph unicode="&#xf19e;" d="M109 1536q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610l13 -707q-62 11 -105 11 q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287z" />
+<glyph unicode="&#xf1a0;" horiz-adv-x="1280" d="M111 182q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5t-59.5 -93 t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131zM297 228q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13 t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5zM403 1222q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5 q53 56 53 159q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160z" />
+<glyph unicode="&#xf1a1;" horiz-adv-x="1984" d="M0 722q0 94 66 160t160 66q83 0 148 -55q248 158 592 164l134 423q4 14 17.5 21.5t28.5 4.5l347 -82q22 50 68.5 81t102.5 31q77 0 131.5 -54.5t54.5 -131.5t-54.5 -132t-131.5 -55q-76 0 -130.5 54t-55.5 131l-315 74l-116 -366q327 -14 560 -166q64 58 151 58 q94 0 160 -66t66 -160q0 -62 -31 -114t-83 -82q5 -33 5 -61q0 -121 -68.5 -230.5t-197.5 -193.5q-125 -82 -285.5 -125.5t-335.5 -43.5q-176 0 -336.5 43.5t-284.5 125.5q-129 84 -197.5 193t-68.5 231q0 29 5 66q-48 31 -77 81.5t-29 109.5zM77 722q0 -67 51 -111 q49 131 180 235q-36 25 -82 25q-62 0 -105.5 -43.5t-43.5 -105.5zM178 465q0 -101 59.5 -194t171.5 -166q116 -75 265.5 -115.5t313.5 -40.5t313.5 40.5t265.5 115.5q112 73 171.5 166t59.5 194t-59.5 193.5t-171.5 165.5q-116 75 -265.5 115.5t-313.5 40.5t-313.5 -40.5 t-265.5 -115.5q-112 -73 -171.5 -165.5t-59.5 -193.5zM555 572q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96zM661 209.5q0 16.5 11 27.5t27 11t27 -11q77 -77 265 -77h2q188 0 265 77q11 11 27 11t27 -11t11 -27.5t-11 -27.5 q-99 -99 -319 -99h-2q-220 0 -319 99q-11 11 -11 27.5zM1153 572q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96zM1555 1350q0 -45 32 -77t77 -32t77 32t32 77t-32 77t-77 32t-77 -32t-32 -77zM1672 843q131 -105 178 -238 q57 46 57 117q0 62 -43.5 105.5t-105.5 43.5q-49 0 -86 -28z" />
+<glyph unicode="&#xf1a2;" d="M0 193v894q0 133 94 227t226 94h896q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227zM155 709q0 -37 19.5 -67.5t52.5 -45.5q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54 q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43 q-51 0 -87 -36.5t-36 -87.5zM493 613q0 37 26 63t63 26t63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64zM560 375q0 11 8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18z M882 613q0 37 26 63t63 26t63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64zM1143 1120q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21q-29 0 -50 21t-21 50z" />
+<glyph unicode="&#xf1a3;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM320 502q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14 q19 0 32.5 -14t13.5 -33v-54l60 -28l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122zM806 500q0 -80 58 -137t139 -57t138.5 57t57.5 139v122h-150v-126q0 -20 -13.5 -33.5t-33.5 -13.5 q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123z" />
+<glyph unicode="&#xf1a4;" horiz-adv-x="1920" d="M0 336v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58l-131 61v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5zM1062 332 v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275h328v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5z" />
+<glyph unicode="&#xf1a5;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM64 640h704v-704h480q93 0 158.5 65.5t65.5 158.5v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480z " />
+<glyph unicode="&#xf1a6;" horiz-adv-x="2048" d="M0 271v697h328v286h204v-983h-532zM205 435h123v369h-123v-369zM614 271h205v697h-205v-697zM614 1050h205v204h-205v-204zM901 26v163h328v82h-328v697h533v-942h-533zM1106 435h123v369h-123v-369zM1516 26v163h327v82h-327v697h532v-942h-532zM1720 435h123v369h-123 v-369z" />
+<glyph unicode="&#xf1a7;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM293 388l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229t-88.5 229t-213.5 95q-74 0 -141 -36h-186v-840z M504 804v277q28 17 70 17q53 0 91 -45t38 -109t-38 -109.5t-91 -45.5q-43 0 -70 15zM636 -39l211 41v206q51 -19 117 -19q125 0 213 95t88 229t-88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101v-636zM847 377v277q28 17 70 17q53 0 91 -45.5t38 -109.5 t-38 -109t-91 -45q-43 0 -70 15z" />
+<glyph unicode="&#xf1a8;" horiz-adv-x="2038" d="M41 455q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80 t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5l1 -21q75 3 143.5 -20.5 t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14q6 -5 28 -23.5t25.5 -22t19 -18 t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63 t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27zM764 676q10 1 32.5 7t34.5 6q19 0 35 -10l-96 -20zM822 568l48 12l109 -177l-73 -48zM859 884q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1 v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5 t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43zM1061 45h31l10 -83l-41 -12v95zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM1116 29 q21 2 60.5 8.5t72 10t60.5 3.5h14q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13t-54 -9.5t-53.5 -7.5t-32 -4.5zM1947 1528l1 3l2 4l-1 -5zM1950 1535v1v-1zM1950 1535l1 1z" />
+<glyph unicode="&#xf1a9;" d="M0 520q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5 t19.5 -177.5q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302zM333 256q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5zM685.5 -76q-0.5 -10 7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5 q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16zM852 31q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5 t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10q-29 -12 -78 -56q-26 -24 -12 -44z" />
+<glyph unicode="&#xf1aa;" d="M0 78q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160l151 -152l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5 t-60 145.5zM2 1202q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5t149.5 -87.5l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5 q-70 15 -115 71t-45 129zM446 803l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126t135.5 51q85 0 145 -60.5t60 -145.5q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152 l-160 -160zM776 793l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5q76 -11 126.5 -68.5t50.5 -134.5q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30 l-152 152z" />
+<glyph unicode="&#xf1ab;" d="M0 -16v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5q20 0 20 -21v-418l147 -47v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3zM39 15l694 232v1032l-694 -233v-1031zM147 293q6 4 82 92 q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8 t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110 q-4 -2 -19.5 -4t-18.5 0zM268 933l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41zM310 -116q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11q73 -37 159.5 -61.5t157.5 -24.5 q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5l-43 73l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16zM777 1294l573 -184v380zM885 453l102 -31l45 110l211 -65l37 -135l102 -31l-181 657l-100 31z M1071 630l76 185l63 -227z" />
+<glyph unicode="&#xf1ac;" horiz-adv-x="1792" d="M0 -96v1088q0 66 47 113t113 47h128q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113zM512 -96v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-163q58 -34 93 -93t35 -128v-768q0 -106 -75 -181 t-181 -75h-864q-66 0 -113 47t-47 113zM640 896h896v256h-160q-40 0 -68 28t-28 68v160h-640v-512zM736 0q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM736 256q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9 h-128q-14 0 -23 -9t-9 -23v-128zM736 512q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM992 0q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM992 256q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM992 512q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM1248 0q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23 v-128zM1248 256q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128zM1248 512q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128z" />
+<glyph unicode="&#xf1ad;" d="M0 -192v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45zM256 160q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM256 416q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM256 672q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM256 928q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM256 1184q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM512 96v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23zM512 416q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64zM512 672q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM512 928q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM512 1184q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM768 416q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM768 672q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM768 928q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM768 1184q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM1024 160q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64zM1024 416q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM1024 672q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM1024 928q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64zM1024 1184q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64z" />
+<glyph unicode="&#xf1ae;" horiz-adv-x="1280" d="M64 1056q0 40 28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68zM416 1152q0 93 65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5t-65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5z" />
+<glyph unicode="&#xf1b0;" horiz-adv-x="1664" d="M0 724q0 80 42 139.5t119 59.5q76 0 141.5 -55.5t100.5 -134t35 -152.5q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152zM256 19q0 86 56 191.5t139.5 192.5t187.5 146t193 59q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45 t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146zM333 1163q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151zM884 1064 q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5q-61 0 -105 39t-63 92.5t-19 113.5zM1226 581q0 74 35 152.5t100.5 134t141.5 55.5q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5 q-77 0 -119 59t-42 139z" />
+<glyph unicode="&#xf1b1;" horiz-adv-x="768" d="M64 1008q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5z" />
+<glyph unicode="&#xf1b2;" horiz-adv-x="1792" d="M0 256v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65zM134 1026l698 -254l698 254l-698 254zM896 -93l640 349v636l-640 -233v-752z" />
+<glyph unicode="&#xf1b3;" horiz-adv-x="2304" d="M0 96v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70v-400l434 -186q36 -16 57 -48t21 -70v-416q0 -36 -19 -67t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4 l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67zM172 531l404 -173l404 173l-404 173zM640 -96l384 192v314l-384 -164v-342zM647 1219l441 -189l441 189l-441 189zM1152 651l384 165v266l-384 -164v-267zM1196 531l404 -173l404 173l-404 173zM1664 -96 l384 192v314l-384 -164v-342z" />
+<glyph unicode="&#xf1b4;" horiz-adv-x="2048" d="M0 22v1260h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5 t45.5 113.5q0 144 -190 144h-260v-294zM1137 477q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658q0 -111 57.5 -171.5t166.5 -60.5q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347zM1337 1073h511v124 h-511v-124zM1388 576h408q-18 195 -200 195q-90 0 -146 -52.5t-62 -142.5z" />
+<glyph unicode="&#xf1b5;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM128 254h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5t-56.5 60.5t-79 29.5 t-97 8.5h-371v-787zM301 388v217h189q124 0 124 -113q0 -104 -128 -104h-185zM301 723v184h163q119 0 119 -90q0 -94 -106 -94h-176zM838 538q0 -135 79 -217t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20q-68 0 -104 38t-36 107h411q1 10 1 30 q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216zM964 911v77h319v-77h-319zM996 600q4 56 39 89t91 33q113 0 124 -122h-254z" />
+<glyph unicode="&#xf1b6;" horiz-adv-x="2048" d="M0 764q0 86 61 146.5t146 60.5q73 0 130 -46t73 -117l783 -315q49 29 106 29q14 0 21 -1l173 248q1 114 82 194.5t195 80.5q115 0 196.5 -81t81.5 -196t-81.5 -196.5t-196.5 -81.5l-265 -194q-8 -80 -67.5 -133.5t-138.5 -53.5q-73 0 -130 46t-73 117l-783 315 q-51 -30 -106 -30q-85 0 -146 61t-61 147zM55 764q0 -64 44.5 -108.5t107.5 -44.5q11 0 33 4l-64 26q-33 14 -52.5 44.5t-19.5 66.5q0 50 35.5 85.5t85.5 35.5q20 0 41 -8v1l76 -31q-20 37 -56.5 59t-78.5 22q-63 0 -107.5 -44.5t-44.5 -107.5zM1164 244q19 -37 55.5 -59 t79.5 -22q63 0 107.5 44.5t44.5 107.5t-44.5 108t-107.5 45q-13 0 -33 -4q2 -1 20 -8t21.5 -8.5t18.5 -8.5t19 -10t16 -11t15.5 -13.5t11 -14.5t10 -18t5 -21t2.5 -25q0 -50 -35.5 -85.5t-85.5 -35.5q-14 0 -31.5 4.5t-29 9t-31.5 13.5t-28 12zM1584 767q0 -77 54.5 -131.5 t131.5 -54.5t132 54.5t55 131.5t-55 131.5t-132 54.5q-76 0 -131 -54.5t-55 -131.5zM1623 767q0 62 43.5 105.5t104.5 43.5t105 -44t44 -105t-43.5 -104.5t-105.5 -43.5q-61 0 -104.5 43.5t-43.5 104.5z" />
+<glyph unicode="&#xf1b7;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM128 693q0 -53 38 -91t92 -38q36 0 66 18l489 -197q10 -44 45.5 -73t81.5 -29q50 0 86.5 34t41.5 83l167 122 q71 0 122 50.5t51 122.5t-51 123t-122 51q-72 0 -122.5 -50.5t-51.5 -121.5l-108 -155q-2 0 -6.5 0.5t-6.5 0.5q-35 0 -67 -19l-489 197q-10 44 -45.5 73t-80.5 29q-54 0 -92 -38t-38 -92zM162 693q0 40 28 68t68 28q27 0 49.5 -14t34.5 -37l-48 19q-29 11 -56.5 -2 t-38.5 -41q-12 -29 -0.5 -57t39.5 -40v-1l40 -16q-14 -2 -20 -2q-40 0 -68 27.5t-28 67.5zM855 369q5 -2 47 -19q29 -12 58 0.5t41 41.5q11 29 -1 57.5t-41 40.5l-40 16q14 2 21 2q39 0 67 -27.5t28 -67.5t-28 -67.5t-67 -27.5q-59 0 -85 51zM1118 695q0 48 34 82t83 34 q48 0 82 -34t34 -82t-34 -82t-82 -34q-49 0 -83 34t-34 82zM1142 696q0 -39 27.5 -66t65.5 -27t65.5 27t27.5 66q0 38 -27.5 65.5t-65.5 27.5t-65.5 -27.5t-27.5 -65.5z" />
+<glyph unicode="&#xf1b8;" horiz-adv-x="1792" d="M16 970l433 -17l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188zM270.5 158q-3.5 28 4 65t12 55t21.5 64t19 53q78 -12 509 -28l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5 q-11 27 -14.5 55zM294 1124l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5t36 -39.5t32 -35q-47 -63 -265 -435l-317 187zM782 1524l405 -1q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190l142 83l-220 -373l-419 20l151 86q-34 89 -75 166 t-75.5 123.5t-64.5 80t-47 46.5zM953 197l211 362l7 -173q170 -16 283 -5t170 33l56 22l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164zM1218 847l313 195l19 11l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22 t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436z" />
+<glyph unicode="&#xf1b9;" horiz-adv-x="1984" d="M0 160v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h704q98 0 179 -63.5t104 -157.5l105 -419h28q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-128v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-928v-128q0 -80 -56 -136 t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23zM160 448q0 -66 47 -113t113 -47t113 47t47 113t-47 113t-113 47t-113 -47t-47 -113zM516 768h952l-89 357q-2 8 -14 17.5t-21 9.5h-704q-9 0 -21 -9.5t-14 -17.5zM1472 448q0 -66 47 -113t113 -47t113 47t47 113 t-47 113t-113 47t-113 -47t-47 -113z" />
+<glyph unicode="&#xf1ba;" horiz-adv-x="1984" d="M0 32v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h64q98 0 179 -63.5t104 -157.5l105 -419h28q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-128v-64q0 -80 -56 -136t-136 -56 t-136 56t-56 136v64h-928v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23zM160 320q0 -66 47 -113t113 -47t113 47t47 113t-47 113t-113 47t-113 -47t-47 -113zM516 640h952l-89 357q-2 8 -14 17.5t-21 9.5h-704q-9 0 -21 -9.5t-14 -17.5zM1472 320 q0 -66 47 -113t113 -47t113 47t47 113t-47 113t-113 47t-113 -47t-47 -113z" />
+<glyph unicode="&#xf1bb;" d="M32 64q0 26 19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45t-19 -45t-45 -19 h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45z" />
+<glyph unicode="&#xf1bc;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM237 886q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37q159 0 309.5 -34t253.5 -95q21 -12 40 -12 q29 0 50.5 20.5t21.5 51.5q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5zM289 637q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5q0 40 -35 61 q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64zM321 406q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52z" />
+<glyph unicode="&#xf1bd;" d="M0 11v1258q0 58 40.5 98.5t98.5 40.5h1258q58 0 98.5 -40.5t40.5 -98.5v-1258q0 -58 -40.5 -98.5t-98.5 -40.5h-1258q-58 0 -98.5 40.5t-40.5 98.5zM71 11q0 -28 20 -48t48 -20h1258q28 0 48 20t20 48v1258q0 28 -20 48t-48 20h-1258q-28 0 -48 -20t-20 -48v-1258z M121 11v141l711 195l-212 439q4 1 12 2.5t12 1.5q170 32 303.5 21.5t221 -46t143.5 -94.5q27 -28 -25 -42q-64 -16 -256 -62l-97 198q-111 7 -240 -16l188 -387l533 145v-496q0 -7 -5.5 -12.5t-12.5 -5.5h-1258q-7 0 -12.5 5.5t-5.5 12.5zM121 709v560q0 7 5.5 12.5 t12.5 5.5h1258q7 0 12.5 -5.5t5.5 -12.5v-428q-85 30 -188 52q-294 64 -645 12l-18 -3l-65 134h-233l85 -190q-132 -51 -230 -137zM246 413q-24 203 166 305l129 -270l-255 -61q-14 -3 -26 4.5t-14 21.5z" />
+<glyph unicode="&#xf1be;" horiz-adv-x="2304" d="M0 405l17 128q2 9 9 9t9 -9l20 -128l-20 -126q-2 -9 -9 -9t-9 9zM79 405l23 207q0 9 9 9q8 0 10 -9l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10zM169 405l21 245q2 12 12 12q11 0 11 -12l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11zM259 405l21 252q0 13 13 13 q12 0 14 -13l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13zM350 405l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5zM401 159zM442 405l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5l21 -380l-21 -246q0 -7 -5 -12.5 t-12 -5.5q-16 0 -18 18zM534 403l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19zM628 405l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5l18 -506l-18 -242q-2 -21 -22 -21q-19 0 -21 21zM723 405l14 -241 q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17zM784 164zM817 405l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18zM913 404l12 492q1 12 9 20t19 8t18.5 -8 t8.5 -20l14 -492l-14 -236q0 -11 -8 -19t-19 -8t-19 8t-9 19zM1010 405q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11q11 0 20 9q9 7 9 20l1 24l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6zM1079 169zM1103 404l12 636v3q2 15 12 24q9 7 20 7 q8 0 15 -5q14 -8 16 -26l14 -639l-14 -231q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114zM1204 174v899q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22z" />
+<glyph unicode="&#xf1c0;" d="M0 0v170q119 -84 325 -127t443 -43t443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128zM0 384v170q119 -84 325 -127t443 -43t443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128zM0 768 v170q119 -84 325 -127t443 -43t443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128zM0 1152v128q0 69 103 128t280 93.5t385 34.5t385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128z" />
+<glyph unicode="&#xf1c1;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM257 60q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4 q52 85 107 197q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38 q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83z M714 842q1 7 7 44q0 3 7 43q1 4 4 8q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2q-15 -42 -2 -132zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1098 353q76 -28 124 -28q14 0 18 1q0 1 -2 3q-24 24 -140 24z" />
+<glyph unicode="&#xf1c2;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM233 661h70l164 -661h159l128 485q7 20 10 46q2 16 2 24 h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21q-2 26 -7 46l-99 438h90v107h-300v-107zM1024 1024h376 q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c3;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM429 0h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4 h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107h-290v-107h68l189 -272l-194 -283h-68v-106zM1024 1024h376 q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c4;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM416 0h327v106h-93v167h137q76 0 118 15q67 23 106.5 87 t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92v-106zM650 386v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15h-119zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c5;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM256 0v192l192 192l128 -128l384 384l320 -320v-320 h-1024zM256 704q0 80 56 136t136 56t136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c6;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536zM384 192q0 25 8 52q21 63 120 396 v128h128v-128h79q22 0 39 -13t23 -34l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5zM512 192q0 -26 37.5 -45t90.5 -19t90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45zM512 896h128v128h-128v-128zM512 1152h128v128h-128v-128 zM640 768h128v128h-128v-128zM640 1024h128v128h-128v-128zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c7;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM256 288v192q0 14 9 23t23 9h131l166 167q16 15 35 7 q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23zM762 206.5q1 -26.5 20 -44.5q20 -17 44 -17q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5z M973.5 54.5q2.5 -26.5 23.5 -42.5q18 -15 40 -15q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c8;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM256 256v384q0 52 38 90t90 38h384q52 0 90 -38t38 -90 v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90zM960 403v90l265 266q9 9 23 9q4 0 12 -2q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1c9;" d="M0 -160v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68zM128 -128h1280v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536zM254 429q-14 19 0 38l226 301q8 11 21 12.5t24 -6.5 l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5zM636 43l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5l-63 10q-13 2 -20.5 13t-5.5 24zM947.5 181 q-1.5 13 6.5 24l182 243l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5l226 -301q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21zM1024 1024h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376z" />
+<glyph unicode="&#xf1ca;" d="M39 1286h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132 t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390z" />
+<glyph unicode="&#xf1cb;" horiz-adv-x="1792" d="M0 367v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64v-546q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64zM154 511l193 129l-193 129v-258zM216 367l603 -402v359l-334 223zM216 913l269 -180l334 223v359zM624 640 l272 -182l272 182l-272 182zM973 -35l603 402l-269 180l-334 -223v-359zM973 956l334 -223l269 180l-603 402v-359zM1445 640l193 -129v258z" />
+<glyph unicode="&#xf1cc;" horiz-adv-x="2048" d="M0 407q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5 h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55t121.5 -21 q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5t-85 -189.5z " />
+<glyph unicode="&#xf1cd;" horiz-adv-x="1792" d="M0 640q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348zM128 640q0 -190 90 -361l194 194q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361zM512 640 q0 -159 112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5zM535 -38q171 -90 361 -90t361 90l-194 194q-82 -28 -167 -28t-167 28zM535 1318l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90t-361 -90z M1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" />
+<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M0 640q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5 q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348z" />
+<glyph unicode="&#xf1d0;" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" />
+<glyph unicode="&#xf1d1;" horiz-adv-x="1792" d="M0 640q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348zM44 640q0 -173 67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331 t-67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331zM87 640q0 205 98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385zM206 217l58 34q29 -49 73 -99 l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13v-66q-208 6 -385 109.5t-283 275.5zM207 1063q106 172 282 275.5t385 109.5v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98zM415 805q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10 t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162q-67 77 -98 169l232 80q-14 42 -14 85t14 85zM918 -102 q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99l58 -34q-106 -172 -283 -275.5t-385 -109.5v66zM918 1382v66q209 -6 385 -109.5t282 -275.5l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13zM1516 428q36 103 36 212q0 112 -35 212l82 28 q-19 56 -49 112l57 33q98 -180 98 -385t-98 -385l-57 33q27 52 49 112z" />
+<glyph unicode="&#xf1d2;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 218q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5 t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85q0 -53 41 -77v-3q-113 -37 -113 -139zM382 225q0 64 98 64q102 0 102 -61q0 -66 -93 -66 q-107 0 -107 63zM395 693q0 90 77 90q36 0 55 -25.5t19 -63.5q0 -85 -74 -85q-77 0 -77 84zM755 1072q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392q0 -50 -3 -75z M966 771q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117z" />
+<glyph unicode="&#xf1d3;" horiz-adv-x="1792" d="M68 7q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47q98 0 218 47v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58q0 -31 22.5 -51.5t58 -32 t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5zM272 18q0 -101 172 -101q151 0 151 105q0 100 -165 100q-158 0 -158 -104zM293 775q0 -135 124 -135q119 0 119 137q0 61 -30 102t-89 41 q-124 0 -124 -145zM875 1389q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5t-39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5zM901 220q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134h-222zM1217 901v190h96v76q0 54 -6 89h227q-6 -41 -6 -165 h171v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6z" />
+<glyph unicode="&#xf1d4;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM368 1135l323 -589v-435h134v436l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3 q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150z" />
+<glyph unicode="&#xf1d5;" horiz-adv-x="1280" d="M57 953q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5t-78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153 t-153 102t-186 38t-186 -38t-153 -102t-102 -153t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265zM113.5 38.5q10.5 121.5 29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5t136.5 -56.5 t56.5 -136.5t-57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5z" />
+<glyph unicode="&#xf1d6;" horiz-adv-x="1792" d="M18 264q0 275 252 466q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5 t3.5 -5t2 -3.5q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9 t-98 20t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20 q-18 -41 -54.5 -74.5t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100z" />
+<glyph unicode="&#xf1d7;" horiz-adv-x="2048" d="M0 858q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490z M380 1075q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5t-25 66t-66 25q-43 0 -76 -25.5t-33 -65.5zM816 404q0 143 81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109q-150 -37 -218 -37 q-169 0 -311 70.5t-223.5 191.5t-81.5 264zM888 1075q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5t-24.5 66t-65.5 25q-43 0 -76 -25.5t-33 -65.5zM1160 568q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5 t-22.5 -49.5zM1559 568q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5z" />
+<glyph unicode="&#xf1d8;" horiz-adv-x="1792" d="M0 508q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55z" />
+<glyph unicode="&#xf1d9;" horiz-adv-x="1792" d="M0 508q-3 39 32 59l1664 960q35 21 68 -2q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55zM209 522l336 -137l863 639l-478 -797l492 -201 l221 1323z" />
+<glyph unicode="&#xf1da;" d="M0 832v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298t-61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12 q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45zM512 480v64q0 14 9 23t23 9h224v352 q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf1db;" d="M0 640q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5zM128 640q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5z" />
+<glyph unicode="&#xf1dc;" horiz-adv-x="1792" d="M62 1338q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5 t45 -15t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18 q-15 10 -45 12t-53 2t-41 14t-18 45z" />
+<glyph unicode="&#xf1dd;" horiz-adv-x="1280" d="M24 926q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108 q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179q-64 117 -64 259z" />
+<glyph unicode="&#xf1de;" d="M0 736v64q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-64q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM128 -96v672h256v-672q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM128 960v416q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-416h-256zM512 224v64q0 40 28 68 t68 28h320q40 0 68 -28t28 -68v-64q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM640 64h256v-160q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v160zM640 448v928q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-928h-256zM1024 992v64q0 40 28 68t68 28h320q40 0 68 -28 t28 -68v-64q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68zM1152 -96v928h256v-928q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23zM1152 1216v160q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-160h-256z" />
+<glyph unicode="&#xf1e0;" d="M0 640q0 133 93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86q133 0 226.5 -93.5t93.5 -226.5 t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5z" />
+<glyph unicode="&#xf1e1;" d="M0 160v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5zM256 640q0 -88 62.5 -150.5t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5 t150.5 62.5t62.5 150.5t-62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5z" />
+<glyph unicode="&#xf1e2;" horiz-adv-x="1792" d="M0 448q0 143 55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68l243 244l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5 t-225 150t-150 225t-55.5 273.5zM170 615q10 -24 35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49t-34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49zM1376 1472q0 13 9 23q10 9 23 9t23 -9l90 -91q10 -9 10 -22.5t-10 -22.5 q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23zM1536 1408v96q0 14 9 23t23 9t23 -9t9 -23v-96q0 -14 -9 -23t-23 -9t-23 9t-9 23zM1605 1242.5q0 13.5 10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5z M1605 1381.5q0 13.5 10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5zM1632 1312q0 14 9 23t23 9h96q14 0 23 -9t9 -23t-9 -23t-23 -9h-96q-14 0 -23 9t-9 23z" />
+<glyph unicode="&#xf1e3;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e4;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e5;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e6;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e7;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e8;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1e9;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1ea;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1eb;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1ec;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1ed;" horiz-adv-x="1792" />
+<glyph unicode="&#xf1ee;" horiz-adv-x="1792" />
+<glyph unicode="&#xf500;" horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+<glyph horiz-adv-x="1792" />
+</font>
+</defs></svg> 
\ No newline at end of file
diff --git a/doc/fonts/fontawesome-webfont.ttf b/doc/fonts/fontawesome-webfont.ttf
new file mode 100755 (executable)
index 0000000..5cd6cff
Binary files /dev/null and b/doc/fonts/fontawesome-webfont.ttf differ
diff --git a/doc/fonts/fontawesome-webfont.woff b/doc/fonts/fontawesome-webfont.woff
new file mode 100755 (executable)
index 0000000..9eaecb3
Binary files /dev/null and b/doc/fonts/fontawesome-webfont.woff differ
index aab29552a66f119b05722b285272fc707e2c1c3d..8aea827473e2a6620b4b98d75ab56b70e5d73451 100644 (file)
Binary files a/doc/images/ssh-adding-public-key.png and b/doc/images/ssh-adding-public-key.png differ
index 3e9cc2d1d1e9cd96d1a3a7e6ffabbea771938a06..7cb0fea15a56d959a0f783a146929040e84778cf 100644 (file)
@@ -12,8 +12,6 @@ For larger scale installations, a manual installation is more appropriate.
 
 h2. Docker
 
-{% include 'alert_docker' %}
-
 "Installing with Docker":install-docker.html
 
 h2. Manual installation
index 0d721fcb0930a6f606e73af57c7fed868a19ddd8..ff877495de710ce4933db4c01e81d651b4057e2b 100644 (file)
@@ -24,9 +24,9 @@ We recommend Ruby >= 2.1.
 <notextile>
 <pre><code><span class="userinput">mkdir -p ~/src
 cd ~/src
-wget http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.1.tar.gz
-tar xzf ruby-2.1.1.tar.gz
-cd ruby-2.1.1
+wget http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz
+tar xzf ruby-2.1.2.tar.gz
+cd ruby-2.1.2
 ./configure
 make
 sudo make install
index ef2b70932146521771dab2391ab3ae8249b5fe9e..00f84eb6e20d8c18a2ed566dc4ba75970abc2ee9 100644 (file)
@@ -4,12 +4,14 @@ navsection: installguide
 title: Installing with Docker
 ...
 
-{% include 'alert_docker' %}
+h2. Purpose
 
-h2. Prerequisites:
+This installation method is appropriate for local testing, evaluation, and development. For production use, this method is not recommended.
+
+h2. Prerequisites
 
 # A GNU/Linux (virtual) machine
-# A working Docker installation
+# A working Docker installation (see "Installing Docker":https://docs.docker.com/installation/)
 
 h2. Download the source tree
 
@@ -22,76 +24,92 @@ See also: "Downloading the source code":https://arvados.org/projects/arvados/wik
 
 h2. Building the Arvados Docker containers
 
-First of all, a suitable @config.yml@ file is needed.
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd arvados/docker</span>
-~$ <span class="userinput">cp config.yml.example config.yml</span>
-</code></pre></notextile>
-
-Edit the @config.yml@ file and fill in values for at a minimum these
-parameters:
-
-<pre>
-PUBLIC_KEY_PATH
-API_HOSTNAME
-API_AUTO_ADMIN_USER
-</pre>
+First of all, a suitable @config.yml@ file is needed. The easiest way to generate one based on the provided @config.yml.example@ file is to run the @build.sh@ script. If no @config.yml@ file exists, it will will prompt for a few inputs, write the @config.yml@ file, and then proceed to build all the Docker containers. If @config.yml@ exists, invoking @build.sh@ will simply build all Docker containers or update those that need to be updated.
 
-Then build the docker containers (this will take a while):
+Run @build.sh@ without arguments to generate @config.yml@ and build all Docker containers (this will take a while!):
 
 <notextile>
 <pre><code>
 ~$ <span class="userinput">./build.sh</span>
+Generating config.yml.
+Arvados needs to know the email address of the administrative user,
+so that when that user logs in they are automatically made an admin.
+This should be an email address associated with a Google account.
+
+Enter your Google ID email address here:
+
 ...
- ---> 05f0ae429530
-Step 9 : ADD apache2_foreground.sh /etc/apache2/foreground.sh
- ---> 7292b241305a
-Step 10 : CMD ["/etc/apache2/foreground.sh"]
- ---> Running in 82d59061ead8
- ---> 72cee36a9281
-Successfully built 72cee36a9281
-Removing intermediate container 2bc8c98c83c7
-Removing intermediate container 9457483a59cf
-Removing intermediate container 7cc5723df67c
-Removing intermediate container 5cb2cede73de
-Removing intermediate container 0acc147a7f6d
-Removing intermediate container 82d59061ead8
-Removing intermediate container 9c022a467396
-Removing intermediate container 16044441463f
-Removing intermediate container cffbbddd82d1
-date >sso-image
+
+Step 7 : ADD generated/setup.sh /usr/local/bin/setup.sh
+ ---> d7c0e7fdf7ab
+Removing intermediate container f3d81180795d
+Step 8 : CMD ["/usr/bin/supervisord", "-n"]
+ ---> Running in 84c64cb9f0d5
+ ---> d6cbb5002604
+Removing intermediate container 84c64cb9f0d5
+Successfully built d6cbb5002604
+date >shell-image
 </code></pre></notextile>
 
 If all goes well, you should now have a number of docker images built:
 
 <notextile>
 <pre><code>~$ <span class="userinput">docker.io images</span>
-REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
-arvados/sso         latest              72cee36a9281        11 seconds ago       1.727 GB
-arvados/keep        latest              c3842f856bcb        56 seconds ago       210.6 MB
-arvados/workbench   latest              b91aa980597c        About a minute ago   2.07 GB
-arvados/doc         latest              050e9e6b8213        About a minute ago   1.442 GB
-arvados/api         latest              79843d0a8997        About a minute ago   2.112 GB
-arvados/passenger   latest              2342a550da7f        2 minutes ago        1.658 GB
-arvados/base        latest              68caefd8ea5b        5 minutes ago        1.383 GB
-arvados/debian      7.5                 6e32119ffcd0        8 minutes ago        116.8 MB
-arvados/debian      latest              6e32119ffcd0        8 minutes ago        116.8 MB
-arvados/debian      wheezy              6e32119ffcd0        8 minutes ago        116.8 MB
+REPOSITORY              TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
+arvados/shell           latest              d6cbb5002604        10 minutes ago      1.613 GB
+arvados/sso             latest              377f1fa0108e        11 minutes ago      1.807 GB
+arvados/keep            latest              ade0e0d2dd00        12 minutes ago      210.8 MB
+arvados/workbench       latest              b0e4fb6da385        12 minutes ago      2.218 GB
+arvados/doc             latest              4b64daec9454        12 minutes ago      1.524 GB
+arvados/compute         latest              7f1f5f7faf54        13 minutes ago      1.862 GB
+arvados/slurm           latest              f5bfd1008e6b        17 minutes ago      1.573 GB
+arvados/api             latest              6b93c5f5fc42        17 minutes ago      2.274 GB
+arvados/passenger       latest              add2d11fdf24        18 minutes ago      1.738 GB
+arvados/base            latest              81eaadd0c6f5        22 minutes ago      1.463 GB
+arvados/debian          7.6                 f339ce275c01        6 days ago          116.8 MB
+arvados/debian          latest              f339ce275c01        6 days ago          116.8 MB
+arvados/debian          wheezy              f339ce275c01        6 days ago          116.8 MB
+crosbymichael/skydock   latest              e985023521f6        3 months ago        510.7 MB
+crosbymichael/skydns    next                79c99a4608ed        3 months ago        525 MB
+crosbymichael/skydns    latest              1923ce648d4c        5 months ago        137.5 MB
 </code></pre></notextile>
 
 h2. Updating the Arvados Docker containers
 
-If there has been an update to the Arvados Docker building code, it is safest to rebuild the Arvados Docker images from scratch. All build information can be cleared with the '--clean' option to build.sh:
+@build.sh@ takes a few optional arguments:
 
 <notextile>
-<pre><code>~$ <span class="userinput">./build.sh --clean</span></code></pre>
+<pre><code>
+~$ <span class="userinput"> ./build.sh --help</span>
+
+usage: ./build.sh [options]
+
+Calling ./build.sh without arguments will build all Arvados docker images
+
+./build.sh options:
+  -h, --help   Print this help text
+  clean        Clear all build information
+  realclean    clean and remove all Arvados Docker images except arvados/debian
+  deepclean    realclean and remove arvados/debian, crosbymichael/skydns and
+               crosbymichael/skydns Docker images
+</code></pre></notextile>
+
+If there has been an update to the Arvados Docker building code, it is safest to rebuild the Arvados Docker images from scratch. All build information can be cleared with the 'clean' option to build.sh:
+
+<notextile>
+<pre><code>~$ <span class="userinput">./build.sh clean</span></code></pre>
 </notextile>
 
-You can also use '--realclean', which does what '--clean' does and in addition removes all Arvados Docker containers and images from your system.
+You can also use 'realclean', which does what 'clean' does and in addition removes all Arvados Docker containers and images from your system, with the exception of the arvados/debian image.
 
 <notextile>
-<pre><code>~$ <span class="userinput">./build.sh --realclean</span></code></pre>
+<pre><code>~$ <span class="userinput">./build.sh realclean</span></code></pre>
+</notextile>
+
+Finally, the 'deepclean' option does what 'realclean' does, and also removes the arvados/debian, crosbymichael/skydns and crosbymichael/skydock images.
+
+<notextile>
+<pre><code>~$ <span class="userinput">./build.sh deepclean</span></code></pre>
 </notextile>
 
 h2. Running the Arvados Docker containers
@@ -105,13 +123,16 @@ The @arvdock@ command can be used to start and stop the docker containers. It ha
 usage: ./arvdock (start|stop|restart|test) [options]
 
 ./arvdock start/stop/restart options:
-  -d [port], --doc[=port]        Documentation server (default port 9898)
-  -w [port], --workbench[=port]  Workbench server (default port 9899)
-  -s [port], --sso[=port]        SSO server (default port 9901)
-  -a [port], --api[=port]        API server (default port 9900)
-  -k, --keep                     Keep servers
-  --ssh                          Enable SSH access to server containers
-  -h, --help                     Display this help and exit
+  -d[port], --doc[=port]        Documentation server (default port 9898)
+  -w[port], --workbench[=port]  Workbench server (default port 9899)
+  -s[port], --sso[=port]        SSO server (default port 9901)
+  -a[port], --api[=port]        API server (default port 9900)
+  -c, --compute                 Compute nodes (starts 2)
+  -v, --vm                      Shell server
+  -n, --nameserver              Nameserver
+  -k, --keep                    Keep servers
+  --ssh                         Enable SSH access to server containers
+  -h, --help                    Display this help and exit
 
   If no options are given, the action is applied to all servers.
 
@@ -166,6 +187,8 @@ Point your browser to the Dockerized workbench:
 </code></pre>
 </notextile>
 
-Now use the google account you specified as @API_AUTO_ADMIN_USER@ in @config.yml@ to log in. You will be prompted *twice* by your browser that you are accessing a site with an untrusted SSL certificate. This is normal; by default the Arvados Docker installation uses self-signed SSL certificates for the SSO and API servers, respectively.
+Now use the google account you specified as @API_AUTO_ADMIN_USER@ in @config.yml@ to log in.
+
+You will be prompted by your browser that you are accessing a site with an untrusted SSL certificate. This is normal; by default the Arvados Docker installation uses self-signed SSL certificates for the SSO and API servers, respectively. If you use a local SSO server in a Docker container, you will be prompted *twice*. The default is to use the Curoverse SSO server.
 
 
diff --git a/doc/sdk/go/index.html.textile.liquid b/doc/sdk/go/index.html.textile.liquid
new file mode 100644 (file)
index 0000000..0e26369
--- /dev/null
@@ -0,0 +1,65 @@
+---
+layout: default
+navsection: sdk
+navmenu: Go
+title: "Go SDK"
+
+...
+
+The Go ("Golang":http://golang.org) SDK provides a generic set of wrappers so you can make API calls easily.
+
+h3. Installation
+
+You don't need to install anything. Just import the client like this. The go tools will fetch the relevant code and dependencies for you.
+
+<notextile>
+<pre><code class="userinput">import (
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+)
+</code></pre>
+</notextile>
+
+h3. Examples
+
+Import the module. (We import the log module here too, so we can use it in the subsequent examples.)
+
+<notextile>
+<pre><code class="userinput">import (
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+       "log"
+)
+</code></pre>
+</notextile>
+
+Set up an API client user agent:
+
+<notextile>
+<pre><code class="userinput">  arv, err := arvadosclient.MakeArvadosClient()
+       if err != nil {
+               log.Fatalf("Error setting up arvados client %s", err.Error())
+       }
+</code></pre>
+</notextile>
+
+Get the User object for the current user:
+
+<notextile>
+<pre><code class="userinput">  type user struct {
+               Uuid         string `json:"uuid"`
+               FullName     int    `json:"full_name"`
+       }
+
+       var u user
+       err := arv.Call("GET", "users", "", "current", nil, &u)
+
+       if err != nil {
+               return err
+       }
+
+       log.Printf("Logged in as %s (uuid %s)", user.Uuid, user.FullName)
+</code></pre>
+</notextile>
+
+A few more usage examples can be found in the services/keepproxy and sdk/go/keepclient directories in the arvados source tree.
index 1b1e18ab9447fbf964e414403003ae980e69926d..db5d6f13b0cbdc08c62f1f156ca70bafdd23e88b 100644 (file)
@@ -10,6 +10,7 @@ This section documents how to access the Arvados API and Keep using various prog
 * "Perl SDK":{{site.baseurl}}/sdk/perl/index.html
 * "Ruby SDK":{{site.baseurl}}/sdk/ruby/index.html
 * "Java SDK":{{site.baseurl}}/sdk/java/index.html
+* "Go SDK":{{site.baseurl}}/sdk/go/index.html
 * "Command line SDK":{{site.baseurl}}/sdk/cli/index.html ("arv")
 
 SDKs not yet implemented:
index 2ab868102c4f8fdce6b5ce5cf57ffc95741ea88f..1ad928c083fde704d7e8fda1cfa471b3f933fcd4 100644 (file)
@@ -4,7 +4,7 @@ navsection: userguide
 title: "Arvados Free Software Licenses"
 ...
 
-Server-side components of Arvados contained in the apps/ and services/ directories, including the API Server, Workbench, and Crunch, are licenced under the "GNU Affero General Public License version 3":agpl-3.0.html.
+Server-side components of Arvados contained in the apps/ and services/ directories, including the API Server, Workbench, Keep, and Crunch, are licenced under the "GNU Affero General Public License version 3":agpl-3.0.html.
 
 The Arvados client Software Development Kits contained in the sdk/ directory, example scripts in the crunch_scripts/ directory, and code samples in the Aravados documentation are licensed under the "Apache License, Version 2.0":LICENSE-2.0.html
 
index 13bb1ae0867482844e9c2dbea7bb81fe3b9b1a22..03e9d7c3453f46075716694e890ac0d2c96966e6 100644 (file)
@@ -1,7 +1,7 @@
 ---
 layout: default
 navsection: userguide
-title: "Crunch examples"
+title: "Scripts provided by Arvados"
 ...
 
 Several crunch scripts are included with Arvados in the "/crunch_scripts directory":https://arvados.org/projects/arvados/repository/revisions/master/show/crunch_scripts. They are intended to provide examples and starting points for writing your own scripts.
index c9d4778c7edc455055a03d96653f2084e2a3ec2d..82b3fa24c18895eb1279265a6150509e9acae19b 100644 (file)
@@ -4,9 +4,9 @@ navsection: userguide
 title: "Checking your environment"
 ...
 
-First you should log into an Arvados VM instance ("Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login) if you have not already done so.
+First, log into an Arvados VM instance (instructions for "Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login) or install the Arvados "Command line SDK":{{site.baseurl}}/sdk/cli/index.html and "Python SDK":{{site.baseurl}}/sdk/python/sdk-python.html on your workstation.
 
-If @arv user current@ is able to access the API server, it will print out information about your account.  Check that you are able to access the Arvados API server using the following command:
+Check that you are able to access the Arvados API server using @arv user current@.  If it is able to access the API server, it will print out information about your account:
 
 <notextile>
 <pre><code>$ <span class="userinput">arv user current</span>
@@ -37,4 +37,4 @@ However, if you receive the following message:
 
 bc. ARVADOS_API_HOST and ARVADOS_API_TOKEN need to be defined as environment variables
 
-Then follow the instructions for "getting an API token,":{{site.baseurl}}/user/reference/api-tokens.html and try @arv user current@ again.
+follow the instructions for "getting an API token,":{{site.baseurl}}/user/reference/api-tokens.html and try @arv user current@ again.
index ccfdb8233a763c306d75da06f51b821448ea4037..02902b01e3aee8c41176b5e63e93c1c5b10729d2 100644 (file)
@@ -4,12 +4,14 @@ navsection: userguide
 title: Accessing Arvados Workbench
 ...
 
-Access the Arvados beta test instance available using this link:
+If you are using the default Arvados instance for this guide, you can Access Arvados Workbench using this link:
 
-"https://{{ site.arvados_workbench_host }}/":https://{{ site.arvados_workbench_host }}/
+<a href="https://{{ site.arvados_workbench_host }}/" target="_blank">https://{{ site.arvados_workbench_host }}/</a>
 
-If you are accessing Arvados for the first time, you will be asked to log in using a Google account.  Arvados uses only your name and email address from Google services for identification, and will never access any personal information.  Once you are logged in, the Workbench page may indicate your account status is *New / inactive*.  If this is the case, contact the administrator of the Arvados instance to activate your account.
+(If you are using a different Arvados instance than the default for this guide, replace *{{ site.arvados_workbench_host }}* with your private instance in all of the examples in this guide.)
 
-Once your account is active, logging in to the Workbench will present you with a system status dashboard. This gives a summary of data, configuration, and activity in the Arvados instance. The dashboard will display a welcome message if you have not yet created any projects. Create a new project by clicking on the <span class="btn btn-primary glyphicon-plus"> Add new project</span>  button in the *My projects* panel in the dashboard.
+You may be asked to log in using a Google account.  Arvados uses only your name and email address from Google services for identification, and will never access any personal information.  If you are accessing Arvados for the first time, the Workbench may indicate your account status is *New / inactive*.  If this is the case, contact the administrator of the Arvados instance to request activation of your account.
+
+Once your account is active, logging in to the Workbench will present you with the *dashboard*. This gives a summary of your projects and recent activity in the Arvados instance.  "You are now ready to run your first pipeline.":{{ site.baseurl }}/user/tutorials/tutorial-pipeline-workbench.html
 
 !{{ site.baseurl }}/images/workbench-dashboard.png!
index 09aa79868cacab9adc49492098a2b722ced5f64d..46a55aec89bdb0f8a23bb57dac62aef7d8806ca5 100644 (file)
@@ -4,30 +4,18 @@ navsection: userguide
 title: Welcome to Arvados!
 ...
 
-This guide is intended to introduce new users to the Arvados system.  It covers initial configuration required to access the system and then presents several tutorials on using Arvados to do data processing.
+_If you are new to Arvados and want to get started quickly, go to "Accessing Arvados Workbench.":{{site.baseurl}}/user/getting_started/workbench.html_
 
-This user guide introduces how to use the major components of Arvados.  These are:
+This guide provides an introduction to using Arvados to solve big data bioinformatics problems, including:
 
-* Keep: Content-addressable cluster file system designed for robust storage of very large files, such as whole genome sequences running in the hundreds of gigabytes
-* Crunch: Cluster compute engine designed for genomic analysis, such as alignment and variant calls
-* Metadata Database: Information about the genomic data stored in Keep, such as genomic traits and human subjects
-* Workbench: Arvados' Web interface
+* Robust storage of very large files, such as whole genome sequences, using the "Arvados Keep":{{site.baseurl}}/user/tutorials/tutorial-keep.html content-addressable cluster file system.
+* Running compute-intensive genomic analysis pipelines, such as alignment and variant calls using the "Arvados Crunch":{{site.baseurl}}/user/tutorials/intro-crunch.html cluster compute engine.
+* Storing and querying metadata about genome sequence files, such as human subjects and their phenotypic traits using the "Arvados Metadata Database.":{{site.baseurl}}/user/topics/tutorial-trait-search.html
+* Accessing, organizing, and sharing data, pipelines and results using the "Arvados Workbench":{{site.baseurl}}/user/getting_started/workbench.html web application.
 
-h2. Prerequisites
+The examples in this guide use the Arvados instance located at <a href="https://{{ site.arvados_workbench_host }}/" target="_blank">https://{{ site.arvados_workbench_host }}</a>.  If you are using a different Arvados instance replace @{{ site.arvados_workbench_host }}@ with your private instance in all of the examples in this guide.
 
-To get the most value out of this guide, you should be comfortable with the following:
-
-# Using a secure shell client such as SSH or PuTTY to log on to a remote server
-# Using the Unix command line shell, Bash
-# Viewing and editing files using a unix text editor such as vi, Emacs, or nano
-# Programming in Python
-# Revision control using Git
-
-We also recommend you read the "Arvados Platform Overview":https://arvados.org/projects/arvados/wiki#Platform-Overview for an introduction and background information about Arvados.
-
-The examples in this guide use the Arvados instance located at "https://{{ site.arvados_workbench_host }}/":https://{{ site.arvados_workbench_host }}/.  If you are using a different Arvados instance replace @{{ site.arvados_workbench_host }}@ with your private instance in all of the examples in this guide.
-
-The Arvados public beta instance is located at "https://workbench.qr1hi.arvadosapi.com/":https://workbench.qr1hi.arvadosapi.com/.  You must have an account in order to use this service.  If you would like to request an account, please send an email to "arvados@curoverse.com":mailto:arvados@curoverse.com.
+Curoverse maintains a public Arvados instance located at <a href="https://workbench.qr1hi.arvadosapi.com/" target="_blank">https://workbench.qr1hi.arvadosapi.com/</a>.  You must have an account in order to use this service.  If you would like to request an account, please send an email to "arvados@curoverse.com":mailto:arvados@curoverse.com.
 
 h2. Typographic conventions
 
@@ -47,4 +35,3 @@ foo.input foo.output
 </li>
 </ul>
 </notextile>
-
index 8a333ffffb1e601292eae0866716ac6db48f174a..5cecd5100d91af09e07d0e978616b91b7948c507 100644 (file)
@@ -8,44 +8,21 @@ The Arvados API token is a secret key that enables the @arv@ command line client
 
 Access the Arvados Workbench using this link: "https://{{ site.arvados_workbench_host }}/":https://{{ site.arvados_workbench_host }}/  (Replace @{{ site.arvados_api_host }}@ with the hostname of your local Arvados instance if necessary.)
 
-Open a shell on the system where you want to use the Arvados client. This may be your local workstation, or an Arvados virtual machine accessed with SSH ("Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login).
+Open a shell on the system where you want to use the Arvados client. This may be your local workstation, or an Arvados virtual machine accessed with SSH (instructions for "Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login).
 
-Click on the user icon <span class="glyphicon glyphicon-user"></span> in the upper right corner to access the user settings menu.  Click on the menu item *Manage API tokens* to go to the "Api client authorizations" page.
+Click on the link with your _email address_ in the upper right corner to access the user settings menu, and click on the menu item *Manage account* to go to the account management page. On the *Manage account* page, you will see the *Current Token* panel, which lists your current token and instructions to setup your environment.
 
-h2. The easy way
+h2. Setting the environment
 
-For your convenience, the "Api client authorizations" page on Workbench provides a *Help* tab that includes a command you may copy and paste directly into the shell.  It will look something like this:
+For your convenience, the "Manage account" page on Workbench provides the "Current Token" panel that includes a command you may copy and paste directly into the shell.  It will look something as the following. 
 
-bc. ### Pasting the following lines at a shell prompt will allow Arvados SDKs
-### to authenticate to your account, you@example.com
-read ARVADOS_API_TOKEN <<EOF
-2jv9346o396exampledonotuseexampledonotuseexes7j1ld
-EOF
-export ARVADOS_API_TOKEN ARVADOS_API_HOST={{ site.arvados_api_host }}
+bc. HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
+export ARVADOS_API_TOKEN=2jv9346o396exampledonotuseexampledonotuseexes7j1ld
+export ARVADOS_API_HOST={{ site.arvados_api_host }}
+export ARVADOS_API_HOST_INSECURE=true
 
-* The @read@ command reads text input until @EOF@ (designated by @<<EOF@) and stores it in the @ARVADOS_API_TOKEN@ environment variable.
 * The @export@ command puts a local shell variable into the environment that will be inherited by child processes such as the @arv@ client.
 
-h2. Setting the environment manually
-
-Alternately, look for the column *api_token* contains the actual token string that you will use.  Copy the value of *api_token* for the first row onto the clipboard.
-
-<notextile>
-<pre><code>$ <span class="userinput">export ARVADOS_API_HOST={{ site.arvados_api_host }}</span>
-$ <span class="userinput">export ARVADOS_API_TOKEN=2jv9346o3966345u7ueuim7a1zaaoueo3w90tzq3gx0es7j1ld</span>
-</code></pre>
-</notextile>
-
-* @ARVADOS_API_HOST@ tells @arv@ which host to connect to.
-* @ARVADOS_API_TOKEN@ is the secret key used by the Arvados API server to authenticate access.  Its value is the text you copied from the *api_token* column on the Workbench.
-
-If you are connecting to a development instance with a unverified/self-signed SSL certificate, set this variable to skip SSL validation:
-
-<notextile>
-<pre><code>$ <span class="userinput">export ARVADOS_API_HOST_INSECURE=1
-</code></pre>
-</notextile>
-
 h2. settings.conf
 
 Arvados tools will also look for the authentication information in @~/.config/arvados/settings.conf@. If you have already put the variables into the environment following the instructions above, you can use these commands to create an Arvados configuration file:
index 3f62254d204bf6f809f20c2d0965cf38ad0d4e81..e696250888fb7c0e04572038b7e59682e6fbe2bf 100644 (file)
@@ -1,7 +1,7 @@
 ---
 layout: default
 navsection: userguide
-title: "Command line interface"
+title: "Command line reference"
 ...
 
 *First, you should be logged into an Arvados VM instance ("Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login), and have a "working environment.":{{site.baseurl}}/user/getting_started/check-environment.html*
index f2ffa62406a19ac34a9a4d730793258692e08efe..9d5cae5afe83c7b8940aad70c4de048041dac495 100644 (file)
@@ -4,11 +4,17 @@ navsection: userguide
 title: "How Keep works"
 ...
 
+The Arvados distributed file system is called *Keep*.  Keep is a content-addressable file system.  This means that files are managed using special unique identifiers derived from the _contents_ of the file (specifically, the MD5 hash), rather than human-assigned file names.  This has a number of advantages:
+* Files can be stored and replicated across a cluster of servers without requiring a central name server.
+* Both the server and client systematically validate data integrity because the checksum is built into the identifier.
+* Data duplication is minimized—two files with the same contents will have in the same identifier, and will not be stored twice.
+* It avoids data race conditions, since an identifier always points to the same data.
+
 In Keep, information is stored in *data blocks*.  Data blocks are normally between 1 byte and 64 megabytes in size.  If a file exceeds the maximum size of a single data block, the file will be split across multiple data blocks until the entire file can be stored.  These data blocks may be stored and replicated across multiple disks, servers, or clusters.  Each data block has its own identifier for the contents of that specific data block.
 
 In order to reassemble the file, Keep stores a *collection* data block which lists in sequence the data blocks that make up the original file.  A collection data block may store the information for multiple files, including a directory structure.
 
-In this example we will use @c1bad4b39ca5a924e481008009d94e32+210@ which we added to Keep in "the first Keep tutorial":{{ site.baseurl }}/user/tutorials/tutorial-keep.html.  First let us examine the contents of this collection using @arv keep get@:
+In this example we will use @c1bad4b39ca5a924e481008009d94e32+210@, which we added to Keep in "how to upload data":{{ site.baseurl }}/user/tutorials/tutorial-keep.html.  First let us examine the contents of this collection using @arv keep get@:
 
 <notextile>
 <pre><code>~$ <span class="userinput">arv keep get c1bad4b39ca5a924e481008009d94e32+210</span>
index 8acce956e9a6225d4b6a9d44724d6f3dfdc86205..c29584228267b6d9c9bb4cd82935ab18af8b837d 100644 (file)
@@ -4,119 +4,40 @@ navsection: userguide
 title: "Running a pipeline on the command line"
 ...
 
-In "Writing a pipeline":{{ site.baseurl }}/user/tutorials/tutorial-firstscript.html, we learned how to create a pipeline template on the command-line.  Let's create one that doesn't require any user input to start:
+This tutorial demonstrates how to use the command line to run the same pipeline as described in "running a pipeline using Workbench.":{{site.baseurl}}/user/tutorials/tutorial-pipeline-workbench.html
 
-<notextile>
-<pre><code>~$ <span class="userinput">cat &gt;the_pipeline &lt;&lt;EOF
-{
-  "name":"Filter MD5 hash values",
-  "components":{
-    "do_hash":{
-      "script":"hash.py",
-      "script_parameters":{
-        "input": "887cd41e9c613463eab2f0d885c6dd96+83"
-      },
-      "repository":"$USER",
-      "script_version":"master"
-    },
-    "filter":{
-      "script":"0-filter.py",
-      "script_parameters":{
-        "input":{
-          "output_of":"do_hash"
-        }
-      },
-      "repository":"$USER",
-      "script_version":"master"
-    }
-  }
-}
-EOF</span>
-~$ <span class="userinput">arv pipeline_template create --pipeline-template "$(cat the_pipeline)"</span></code></pre>
-</notextile>
-
-(Your shell should automatically fill in @$USER@ with your login name.  The JSON that gets saved should have @"repository"@ pointed at your personal Git repository.)
-
-You can run this pipeline from the command line using @arv pipeline run@, filling in the UUID that you received from @arv pipeline_template create@:
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv pipeline run --run-here --template qr1hi-p5p6p-xxxxxxxxxxxxxxx</span>
-2013-12-16 14:08:40 +0000 -- pipeline_instance qr1hi-d1hrv-vxzkp38nlde9yyr
-do_hash qr1hi-8i9sb-hoyc2u964ecv1s6 queued 2013-12-16T14:08:40Z
-filter  -                           -
+{% include 'tutorial_expectations' %}
 
-2013-12-16 14:08:51 +0000 -- pipeline_instance qr1hi-d1hrv-vxzkp38nlde9yyr
-do_hash qr1hi-8i9sb-hoyc2u964ecv1s6 1ed9ed18ef31ad21bcabcfeff7777bae+162
-filter  qr1hi-8i9sb-w5k40fztqgg9i2x queued 2013-12-16T14:08:50Z
+When you use the command line, you must use Arvados unique identifiers to refer to objects.  The identifiers in this example correspond to the following Arvados objects:
 
-2013-12-16 14:09:01 +0000 -- pipeline_instance qr1hi-d1hrv-vxzkp38nlde9yyr
-do_hash qr1hi-8i9sb-hoyc2u964ecv1s6 1ed9ed18ef31ad21bcabcfeff7777bae+162
-filter  qr1hi-8i9sb-w5k40fztqgg9i2x d3bcc2ee0f0ea31049000c721c0f3a2a+56
-</code></pre>
-</notextile>
-
-This instantiates your pipeline and displays a live feed of its status.  The new pipeline instance will also show up on Workbench *Activity* %(rarr)&rarr;% *Recent&nbsp;pipeline&nbsp;instances* page.
+* <i class="fa fa-fw fa-gear"></i> "Tutorial align using bwa mem (qr1hi-p5p6p-itzkwxblfermlwv)":https://{{ site.arvados_workbench_host }}/pipeline_templates/qr1hi-p5p6p-itzkwxblfermlwv
+* <i class="fa fa-fw fa-archive"></i> "Tutorial chromosome 19 reference (2463fa9efeb75e099685528b3b9071e0+438)":https://{{ site.arvados_workbench_host }}/collections/2463fa9efeb75e099685528b3b9071e0+438
+* <i class="fa fa-fw fa-archive"></i> "Tutorial sample exome (3229739b505d2b878b62aed09895a55a+142)":https://{{ site.arvados_workbench_host }}/collections/3229739b505d2b878b62aed09895a55a+142
 
-Arvados adds each pipeline component to the job queue as its dependencies are satisfied (or immediately if it has no dependencies) and finishes when all components are completed or failed and there is no more work left to do.
-
-The Keep locators of the output of each of @"do_hash"@ and @"filter"@ component are available from the output log shown above.  The output is also available on the Workbench by navigating to *Activity* %(rarr)&rarr;% *Recent&nbsp;pipeline&nbsp;instances* %(rarr)&rarr;% pipeline UUID under the *Instance* column %(rarr)&rarr;% *output* column.
+Use @arv pipeline run@ to run the pipeline, supplying the inputs to the bwa-mem component on the command line:
 
 <notextile>
-<pre><code>~$ <span class="userinput">arv keep get 1ed9ed18ef31ad21bcabcfeff7777bae+162/md5sum.txt</span>
-0f1d6bcf55c34bed7f92a805d2d89bbf 887cd41e9c613463eab2f0d885c6dd96+83/./alice.txt
-504938460ef369cd275e4ef58994cffe 887cd41e9c613463eab2f0d885c6dd96+83/./bob.txt
-8f3b36aff310e06f3c5b9e95678ff77a 887cd41e9c613463eab2f0d885c6dd96+83/./carol.txt
-~$ <span class="userinput">arv keep get d3bcc2ee0f0ea31049000c721c0f3a2a+56/0-filter.txt</span>
-0f1d6bcf55c34bed7f92a805d2d89bbf 887cd41e9c613463eab2f0d885c6dd96+83/./alice.txt
-</code></pre>
-</notextile>
-
-Indeed, the filter has picked out just the "alice" file as having a hash that starts with 0.
+<pre><code>~$ <span class="userinput">arv pipeline run --run-here --template qr1hi-p5p6p-itzkwxblfermlwv bwa-mem::reference_collection=2463fa9efeb75e099685528b3b9071e0+438 bwa-mem::sample=3229739b505d2b878b62aed09895a55a+142</span>
 
-h3. Running a pipeline with different parameters
+2014-07-25 18:05:26 +0000 -- pipeline_instance qr1hi-d1hrv-d14trje19pna7f2
+bwa-mem qr1hi-8i9sb-67n1qvsronmd2z6 queued 2014-07-25T18:05:25Z
 
-Notice that the pipeline template explicitly specifies the Keep locator for the input:
+2014-07-25 18:05:36 +0000 -- pipeline_instance qr1hi-d1hrv-d14trje19pna7f2
+bwa-mem qr1hi-8i9sb-67n1qvsronmd2z6 {:done=>0, :running=>1, :failed=>0, :todo=>0}
 
-<notextile>
-<pre><code>...
-    "do_hash":{
-      "script_parameters":{
-        "input": "887cd41e9c613463eab2f0d885c6dd96+83"
-      },
-    }
-...
+2014-07-25 18:05:46 +0000 -- pipeline_instance qr1hi-d1hrv-d14trje19pna7f2
+bwa-mem qr1hi-8i9sb-67n1qvsronmd2z6 49bae1066f4ebce72e2587a3efa61c7d+88
 </code></pre>
 </notextile>
 
-You can specify values for pipeline component script_parameters like this:
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv pipeline run --run-here --template qr1hi-p5p6p-xxxxxxxxxxxxxxx do_hash::input=c1bad4b39ca5a924e481008009d94e32+210</span>
-2013-12-17 20:31:24 +0000 -- pipeline_instance qr1hi-d1hrv-tlkq20687akys8e
-do_hash qr1hi-8i9sb-rffhuay4jryl2n2 queued 2013-12-17T20:31:24Z
-filter  -                           -
-
-2013-12-17 20:31:34 +0000 -- pipeline_instance qr1hi-d1hrv-tlkq20687akys8e
-do_hash qr1hi-8i9sb-rffhuay4jryl2n2 {:done=>1, :running=>1, :failed=>0, :todo=>0}
-filter  -                           -
-
-2013-12-17 20:31:55 +0000 -- pipeline_instance qr1hi-d1hrv-tlkq20687akys8e
-do_hash qr1hi-8i9sb-rffhuay4jryl2n2 50cafdb29cc21dd6eaec85ba9e0c6134+56
-filter  qr1hi-8i9sb-j347g1sqovdh0op queued 2013-12-17T20:31:55Z
+This instantiates your pipeline and displays a live feed of its status.  The new pipeline instance will also show up on Workbench dashboard.
 
-2013-12-17 20:32:05 +0000 -- pipeline_instance qr1hi-d1hrv-tlkq20687akys8e
-do_hash qr1hi-8i9sb-rffhuay4jryl2n2 50cafdb29cc21dd6eaec85ba9e0c6134+56
-filter  qr1hi-8i9sb-j347g1sqovdh0op 490cd451c8108824b8a17e3723e1f236+19
-</code></pre>
-</notextile>
+Arvados adds each pipeline component to the job queue as its dependencies are satisfied (or immediately if it has no dependencies) and finishes when all components are completed or failed and there is no more work left to do.
 
-Now check the output:
+The Keep locators of the output of of the @bwa-mem@ components are available from the status feed shown above:
 
 <notextile>
-<pre><code>~$ <span class="userinput">arv keep get 50cafdb29cc21dd6eaec85ba9e0c6134+56/md5sum.txt</span>
-44b8ae3fde7a8a88d2f7ebd237625b4f c1bad4b39ca5a924e481008009d94e32+210/./var-GS000016015-ASM.tsv.bz2
-~$ <span class="userinput">arv keep get 490cd451c8108824b8a17e3723e1f236+19/0-filter.txt</span>
+<pre><code>~$ <span class="userinput">arv keep ls -s 49bae1066f4ebce72e2587a3efa61c7d+88</span>
+     29226 ./HWI-ST1027_129_D0THKACXX.1_1.sam
 </code></pre>
 </notextile>
-
-Since none of the files in the collection have hash code that start with 0, the output of the filter component is empty.
diff --git a/doc/user/topics/tutorial-job-debug.html.textile.liquid b/doc/user/topics/tutorial-job-debug.html.textile.liquid
deleted file mode 100644 (file)
index 772a05e..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
----
-layout: default
-navsection: userguide
-title: "Debugging a Crunch script"
-...
-
-To test changes to a script by running a job, the change must be pushed to your hosted repository, and the job might have to wait in the queue before it runs. This cycle can be an inefficient way to develop and debug scripts. This tutorial demonstrates an alternative: using @arv-crunch-job@ to run your job in your local VM.  This avoids the job queue and allows you to execute the script directly from your git working tree without committing or pushing.
-
-{% include 'tutorial_expectations' %}
-
-This tutorial uses *@you@* to denote your username.  Replace *@you@* with your user name in all the following examples.
-
-h2. Create a new script
-
-Change to your Git working directory and create a new script in @crunch_scripts/@.
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd <b>you</b>/crunch_scripts</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;hello-world.py &lt;&lt;EOF
-#!/usr/bin/env python
-
-print "hello world"
-print "this script will fail, and that is expected!"
-EOF</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x hello-world.py</span>
-</code></pre>
-</notextile>
-
-h2. Using arv-crunch-job to run the job in your VM
-
-Instead of a Git commit hash, we provide the path to the directory in the "script_version" parameter.  The script specified in "script" is expected to be in the @crunch_scripts/@ subdirectory of the directory specified "script_version".  Although we are running the script locally, the script still requires access to the Arvados API server and Keep storage service. The job will be recorded in the Arvados job history, and visible in Workbench.
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;~/the_job &lt;&lt;EOF
-{
- "repository":"",
- "script":"hello-world.py",
- "script_version":"$HOME/$USER",
- "script_parameters":{}
-}
-EOF</span>
-</code></pre>
-</notextile>
-
-Your shell should fill in values for @$HOME@ and @$USER@ so that the saved JSON points "script_version" at the directory with your checkout.  Now you can run that job:
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts</span>$ <span class="userinput">arv-crunch-job --job "$(cat ~/the_job)"</span>
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  check slurm allocation
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  node localhost - 1 slots
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  start
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  script hello-world.py
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  script_version /home/<b>you</b>/<b>you</b>
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  script_parameters {}
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  runtime_constraints {"max_tasks_per_node":0}
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  start level 0
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  status: 0 done, 0 running, 1 todo
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 job_task qr1hi-ot0gb-4zdajby8cjmlguh
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 child 29834 started on localhost.1
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827  status: 0 done, 1 running, 0 todo
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 stderr hello world
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 stderr this script will fail, and that is expected!
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 child 29834 on localhost.1 exit 0 signal 0 success=
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 failure (#1, permanent) after 0 seconds
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 output
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  Every node has failed -- giving up on this round
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  wait for last 0 children to finish
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  status: 0 done, 0 running, 0 todo
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  Freeze not implemented
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  collate
-2013-12-12_21:36:43 qr1hi-8i9sb-okzukfzkpbrnhst 29827  output d41d8cd98f00b204e9800998ecf8427e+0
-2013-12-12_21:36:44 qr1hi-8i9sb-okzukfzkpbrnhst 29827  meta key is c00bfbd58e6f58ce3bebdd47f745a70f+1857
-</code></pre>
-</notextile>
-
-These are the lines of interest:
-
-bc. 2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 stderr hello world
-2013-12-12_21:36:42 qr1hi-8i9sb-okzukfzkpbrnhst 29827 0 stderr this script will fail, and that is expected!
-
-The script's output is captured in the log, which is useful for print statement debugging. However, although this script returned a status code of 0 (success), the job failed.  Why?  For a job to complete successfully scripts must explicitly add their output to Keep, and then tell Arvados about it.  Here is a second try:
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;hello-world-fixed.py &lt;&lt;EOF
-#!/usr/bin/env python
-
-import arvados
-
-# Create a new collection
-out = arvados.CollectionWriter()
-
-# Set the name of the file in the collection to write to
-out.set_current_file_name('hello.txt')
-
-# Actually output our text
-out.write('hello world')
-
-# Commit the collection to Keep
-out_collection = out.finish()
-
-# Tell Arvados which Keep object is our output
-arvados.current_task().set_output(out_collection)
-
-# Done!
-EOF</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x hello-world-fixed.py</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;~/the_job &lt;&lt;EOF
-{
- "repository":"",
- "script":"hello-world-fixed.py",
- "script_version":"$HOME/$USER",
- "script_parameters":{}
-}
-EOF</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">arv-crunch-job --job "$(cat ~/the_job)"</span>
-2013-12-12_21:56:59 qr1hi-8i9sb-79260ykfew5trzl 31578  check slurm allocation
-2013-12-12_21:56:59 qr1hi-8i9sb-79260ykfew5trzl 31578  node localhost - 1 slots
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  start
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  script hello-world-fixed.py
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  script_version /home/<b>you</b>/<b>you</b>
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  script_parameters {}
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  runtime_constraints {"max_tasks_per_node":0}
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  start level 0
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  status: 0 done, 0 running, 1 todo
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578 0 job_task qr1hi-ot0gb-u8g594ct0wt7f3f
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578 0 child 31585 started on localhost.1
-2013-12-12_21:57:00 qr1hi-8i9sb-79260ykfew5trzl 31578  status: 0 done, 1 running, 0 todo
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578 0 child 31585 on localhost.1 exit 0 signal 0 success=true
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578 0 success in 1 seconds
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578 0 output 576c44d762ba241b0a674aa43152b52a+53
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578  wait for last 0 children to finish
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578  status: 1 done, 0 running, 0 todo
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578  Freeze not implemented
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578  collate
-2013-12-12_21:57:02 qr1hi-8i9sb-79260ykfew5trzl 31578  output 576c44d762ba241b0a674aa43152b52a+53
-WARNING:root:API lookup failed for collection 576c44d762ba241b0a674aa43152b52a+53 (<class 'apiclient.errors.HttpError'>: <HttpError 404 when requesting https://qr1hi.arvadosapi.com/arvados/v1/collections/576c44d762ba241b0a674aa43152b52a%2B53?alt=json returned "Not Found">)
-2013-12-12_21:57:03 qr1hi-8i9sb-79260ykfew5trzl 31578  finish
-</code></pre>
-</notextile>
-
-(The WARNING issued near the end of the script may be safely ignored here; it is the Arvados SDK letting you know that it could not find a collection named @576c44d762ba241b0a674aa43152b52a+53@ and that it is going to try looking up a block by that name instead.)
-
-The job succeeded, with output in Keep object @576c44d762ba241b0a674aa43152b52a+53@.  Let's look at our output:
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">arv keep get 576c44d762ba241b0a674aa43152b52a+53/hello.txt</span>
-hello world
-</code></pre>
-</notextile>
-
-h3. Location of temporary files
-
-Crunch job tasks are supplied with @TASK_WORK@ and @JOB_WORK@ environment variables, to be used as scratch space.  When running in local development mode using @arv-crunch-job@, Crunch sets these variables to point to directory called @crunch-job-{USERID}@ in @TMPDIR@ (or @/tmp@ if @TMPDIR@ is not set).
-
-* Set @TMPDIR@ to @/scratch@ to make Crunch use a directory like @/scratch/crunch-job-{USERID}/@ for temporary space.
-
-* Set @CRUNCH_TMP@ to @/scratch/foo@ to make Crunch use @/scratch/foo/@ for temporary space (omitting the default @crunch-job-{USERID}@ leaf name)
-
-h3. Testing job scripts without SDKs and Keep access
-
-Read and write data to @/tmp/@ instead of Keep. This only works with the Python SDK.
-
-notextile. <pre><code>~$ <span class="userinput">export KEEP_LOCAL_STORE=/tmp</span></code></pre>
-
index d7a093a245085bf650cd6ee9b0ae1896e7313939..9be610358bb5f8133c6f7269c390799e04de8e5d 100644 (file)
@@ -1,45 +1,45 @@
 ---
 layout: default
 navsection: userguide
-title: "Parallel Crunch tasks"
+title: "Concurrent Crunch tasks"
 ...
 
-In the previous tutorials, we used @arvados.job_setup.one_task_per_input_file()@ to automatically parallelize our jobs by creating a separate task per file.  For some types of jobs, you may need to split the work up differently, for example creating tasks to process different segments of a single large file.  In this this tutorial will demonstrate how to create Crunch tasks directly.
+In the previous tutorials, we used @arvados.job_setup.one_task_per_input_file()@ to automatically create concurrent jobs by creating a separate task per file.  For some types of jobs, you may need to split the work up differently, for example creating tasks to process different segments of a single large file.  In this this tutorial will demonstrate how to create Crunch tasks directly.
 
 Start by entering the @crunch_scripts@ directory of your Git repository:
 
 <notextile>
-<pre><code>~$ <span class="userinput">cd <b>you</b>/crunch_scripts</span>
+<pre><code>~$ <span class="userinput">cd $USER/crunch_scripts</span>
 </code></pre>
 </notextile>
 
-Next, using @nano@ or your favorite Unix text editor, create a new file called @parallel-hash.py@ in the @crunch_scripts@ directory.
+Next, using @nano@ or your favorite Unix text editor, create a new file called @concurrent-hash.py@ in the @crunch_scripts@ directory.
 
-notextile. <pre>~/<b>you</b>/crunch_scripts$ <code class="userinput">nano parallel-hash.py</code></pre>
+notextile. <pre>~/$USER/crunch_scripts$ <code class="userinput">nano concurrent-hash.py</code></pre>
 
 Add the following code to compute the MD5 hash of each file in a collection:
 
-<notextile> {% code 'parallel_hash_script_py' as python %} </notextile>
+<notextile> {% code 'concurrent_hash_script_py' as python %} </notextile>
 
 Make the file executable:
 
-notextile. <pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x parallel-hash.py</span></code></pre>
+notextile. <pre><code>~/$USER/crunch_scripts$ <span class="userinput">chmod +x concurrent-hash.py</span></code></pre>
 
 Add the file to the Git staging area, commit, and push:
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">git add parallel-hash.py</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git commit -m"parallel hash"</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git push origin master</span>
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">git add concurrent-hash.py</span>
+~/$USER/crunch_scripts$ <span class="userinput">git commit -m"concurrent hash"</span>
+~/$USER/crunch_scripts$ <span class="userinput">git push origin master</span>
 </code></pre>
 </notextile>
 
-You should now be able to run your new script using Crunch, with "script" referring to our new "parallel-hash.py" script.  We will use a different input from our previous examples.  We will use @887cd41e9c613463eab2f0d885c6dd96+83@ which consists of three files, "alice.txt", "bob.txt" and "carol.txt" (the example collection used previously in "fetching data from Arvados using Keep":{{site.baseurl}}/user/tutorials/tutorial-keep.html#dir).
+You should now be able to run your new script using Crunch, with "script" referring to our new "concurrent-hash.py" script.  We will use a different input from our previous examples.  We will use @887cd41e9c613463eab2f0d885c6dd96+83@ which consists of three files, "alice.txt", "bob.txt" and "carol.txt" (the example collection used previously in "fetching data from Arvados using Keep":{{site.baseurl}}/user/tutorials/tutorial-keep.html#dir).
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;~/the_job &lt;&lt;EOF
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">cat &gt;~/the_job &lt;&lt;EOF
 {
- "script": "parallel-hash.py",
+ "script": "concurrent-hash.py",
  "repository": "$USER",
  "script_version": "master",
  "script_parameters":
@@ -48,13 +48,13 @@ You should now be able to run your new script using Crunch, with "script" referr
  }
 }
 EOF</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">arv job create --job "$(cat ~/the_job)"</span>
+~/$USER/crunch_scripts$ <span class="userinput">arv job create --job "$(cat ~/the_job)"</span>
 {
  ...
  "uuid":"qr1hi-xxxxx-xxxxxxxxxxxxxxx"
  ...
 }
-~/<b>you</b>/crunch_scripts$ <span class="userinput">arv job get --uuid qr1hi-xxxxx-xxxxxxxxxxxxxxx</span>
+~/$USER/crunch_scripts$ <span class="userinput">arv job get --uuid qr1hi-xxxxx-xxxxxxxxxxxxxxx</span>
 {
  ...
  "output":"e2ccd204bca37c77c0ba59fc470cd0f7+162",
@@ -65,16 +65,14 @@ EOF</span>
 
 (Your shell should automatically fill in @$USER@ with your login name.  The job JSON that gets saved should have @"repository"@ pointed at your personal Git repository.)
 
-Because the job ran in parallel, each instance of parallel-hash creates a separate @md5sum.txt@ as output.  Arvados automatically collates theses files into a single collection, which is the output of the job:
+Because the job ran in concurrent, each instance of concurrent-hash creates a separate @md5sum.txt@ as output.  Arvados automatically collates theses files into a single collection, which is the output of the job:
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">arv keep ls e2ccd204bca37c77c0ba59fc470cd0f7+162</span>
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">arv keep ls e2ccd204bca37c77c0ba59fc470cd0f7+162</span>
 ./md5sum.txt
-~/<b>you</b>/crunch_scripts$ <span class="userinput">arv keep get e2ccd204bca37c77c0ba59fc470cd0f7+162/md5sum.txt</span>
+~/$USER/crunch_scripts$ <span class="userinput">arv keep get e2ccd204bca37c77c0ba59fc470cd0f7+162/md5sum.txt</span>
 0f1d6bcf55c34bed7f92a805d2d89bbf alice.txt
 504938460ef369cd275e4ef58994cffe bob.txt
 8f3b36aff310e06f3c5b9e95678ff77a carol.txt
 </code></pre>
 </notextile>
-
-
index a95e30ddfd4b8e416bdbf23565b2df65dbe32de9..d1a0e2434c71dff0e318c8c0d8c301ce6ca96258 100644 (file)
@@ -137,13 +137,13 @@ h2. Find Personal Genome Project identifiers from Arvados UUIDs
 
 These PGP IDs let us find public profiles, for example:
 
-* "https://my.personalgenomes.org/profile/huE2E371":https://my.personalgenomes.org/profile/huE2E371
-* "https://my.personalgenomes.org/profile/huDF04CC":https://my.personalgenomes.org/profile/huDF04CC
+* "https://my.pgp-hms.org/profile/huE2E371":https://my.pgp-hms.org/profile/huE2E371
+* "https://my.pgp-hms.org/profile/huDF04CC":https://my.pgp-hms.org/profile/huDF04CC
 * ...
 
 h2. Find genomic data from specific humans
 
-Now we want to find collections in Keep that were provided by these humans.  We search the "links" resource for "provenance" links that point to subjects in list of humans with the non-melanoma skin cancer trait:
+Now we want to find collections in Keep that were provided by these humans.  We search the "links" resource for "provenance" links that point to entries in the list of humans with the non-melanoma skin cancer trait:
 
 <notextile>
 <pre><code>&gt;&gt;&gt; <span class="userinput">provenance_links = arvados.api().links().list(limit=1000, filters=[
@@ -170,26 +170,26 @@ collections = arvados.api('v1').collections().list(filters=[
 # print PGP public profile links with file locators
 for c in collections['items']:
   for f in c['files']:
-    print "https://my.personalgenomes.org/profile/%s %s %s%s" % (pgpid[c['uuid']], c['uuid'], ('' if f[0] == '.' else f[0]+'/'), f[1])
+    print "https://my.pgp-hms.org/profile/%s %s %s%s" % (pgpid[c['uuid']], c['uuid'], ('' if f[0] == '.' else f[0]+'/'), f[1])
 </span>
-https://my.personalgenomes.org/profile/hu43860C a58dca7609fa84c8c38a7e926a97b2fc var-GS00253-DNA_A01_200_37-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huB1FD55 ea30eb9e46eedf7f05ed6e348c2baf5d var-GS000010320-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huDF04CC 4ab0df8f22f595d1747a22c476c05873 var-GS000010427-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu7A2F1D 756d0ada29b376140f64e7abfe6aa0e7 var-GS000014566-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu553620 7ed4e425bb1c7cc18387cbd9388181df var-GS000015272-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huD09534 542112e210daff30dd3cfea4801a9f2f var-GS000016374-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu599905 33a9f3842b01ea3fdf27cc582f5ea2af var-GS000016015-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu43860C a58dca7609fa84c8c38a7e926a97b2fc+302 var-GS00253-DNA_A01_200_37-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huB1FD55 ea30eb9e46eedf7f05ed6e348c2baf5d+291 var-GS000010320-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huDF04CC 4ab0df8f22f595d1747a22c476c05873+242 var-GS000010427-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu7A2F1D 756d0ada29b376140f64e7abfe6aa0e7+242 var-GS000014566-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu553620 7ed4e425bb1c7cc18387cbd9388181df+242 var-GS000015272-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/huD09534 542112e210daff30dd3cfea4801a9f2f+242 var-GS000016374-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu599905 33a9f3842b01ea3fdf27cc582f5ea2af+242 var-GS000016015-ASM.tsv.bz2
-https://my.personalgenomes.org/profile/hu599905 d6e2e57cd60ba5979006d0b03e45e726+81 Witch_results.zip
-https://my.personalgenomes.org/profile/hu553620 ea4f2d325592a1272f989d141a917fdd+85 Devenwood_results.zip
-https://my.personalgenomes.org/profile/hu7A2F1D 4580f6620bb15b25b18373766e14e4a7+85 Innkeeper_results.zip
-https://my.personalgenomes.org/profile/huD09534 fee37be9440b912eb90f5e779f272416+82 Hallet_results.zip
+https://my.pgp-hms.org/profile/hu43860C a58dca7609fa84c8c38a7e926a97b2fc var-GS00253-DNA_A01_200_37-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huB1FD55 ea30eb9e46eedf7f05ed6e348c2baf5d var-GS000010320-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huDF04CC 4ab0df8f22f595d1747a22c476c05873 var-GS000010427-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu7A2F1D 756d0ada29b376140f64e7abfe6aa0e7 var-GS000014566-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu553620 7ed4e425bb1c7cc18387cbd9388181df var-GS000015272-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huD09534 542112e210daff30dd3cfea4801a9f2f var-GS000016374-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu599905 33a9f3842b01ea3fdf27cc582f5ea2af var-GS000016015-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu43860C a58dca7609fa84c8c38a7e926a97b2fc+302 var-GS00253-DNA_A01_200_37-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huB1FD55 ea30eb9e46eedf7f05ed6e348c2baf5d+291 var-GS000010320-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huDF04CC 4ab0df8f22f595d1747a22c476c05873+242 var-GS000010427-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu7A2F1D 756d0ada29b376140f64e7abfe6aa0e7+242 var-GS000014566-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu553620 7ed4e425bb1c7cc18387cbd9388181df+242 var-GS000015272-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/huD09534 542112e210daff30dd3cfea4801a9f2f+242 var-GS000016374-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu599905 33a9f3842b01ea3fdf27cc582f5ea2af+242 var-GS000016015-ASM.tsv.bz2
+https://my.pgp-hms.org/profile/hu599905 d6e2e57cd60ba5979006d0b03e45e726+81 Witch_results.zip
+https://my.pgp-hms.org/profile/hu553620 ea4f2d325592a1272f989d141a917fdd+85 Devenwood_results.zip
+https://my.pgp-hms.org/profile/hu7A2F1D 4580f6620bb15b25b18373766e14e4a7+85 Innkeeper_results.zip
+https://my.pgp-hms.org/profile/huD09534 fee37be9440b912eb90f5e779f272416+82 Hallet_results.zip
 </code></pre>
 </notextile>
 
index e3eca1649d4ab7c203600655419642b24f838130..a6780830044c4bf550caf49b9908e9e708093986 100644 (file)
@@ -4,14 +4,21 @@ navsection: userguide
 title: Introduction to Crunch
 ...
 
-In "getting data from Keep,":tutorial-keep.html#arv-get we downloaded a file from Keep and did some computation with it (specifically, computing the MD5 hash of the complete file).  While a straightforward way to accomplish a computational task, there are several obvious drawbacks to this approach:
-* Large files require significant time to download.
-* Very large files may exceed the scratch space of the local disk.
-* We are only able to use the local CPU to process the file.
-
 The Arvados "Crunch" framework is designed to support processing very large data batches (gigabytes to terabytes) efficiently, and provides the following benefits:
 * Increase concurrency by running tasks asynchronously, using many CPUs and network interfaces at once (especially beneficial for CPU-bound and I/O-bound tasks respectively).
 * Track inputs, outputs, and settings so you can verify that the inputs, settings, and sequence of programs you used to arrive at an output is really what you think it was.
 * Ensure that your programs and workflows are repeatable with different versions of your code, OS updates, etc.
 * Interrupt and resume long-running jobs consisting of many short tasks.
 * Maintain timing statistics automatically, so they're there when you want them.
+
+h2. Prerequisites
+
+To get the most value out of this section, you should be comfortable with the following:
+
+# Using a secure shell client such as SSH or PuTTY to log on to a remote server
+# Using the Unix command line shell, Bash
+# Viewing and editing files using a unix text editor such as vi, Emacs, or nano
+# Programming in Python
+# Revision control using Git
+
+We also recommend you read the "Arvados Platform Overview":https://arvados.org/projects/arvados/wiki#Platform-Overview for an introduction and background information about Arvados.
index e683dea8dc58f38e9320557fa404d5ca8309bf18..3b9cd165f28562bcafc08db33aa26e481ebb2118 100644 (file)
@@ -1,68 +1,53 @@
 ---
 layout: default
 navsection: userguide
-title: "Using Crunch to run external programs"
+title: "Writing a pipeline template"
 ...
 
-This tutorial demonstrates how to use Crunch to run an external program by writting a wrapper using the Python SDK.
+This tutorial demonstrates how to construct a two stage pipeline template that uses the "bwa mem":http://bio-bwa.sourceforge.net/ tool to produce a "Sequence Alignment/Map (SAM)":https://samtools.github.io/ file, then uses the "Picard SortSam tool":http://picard.sourceforge.net/command-line-overview.shtml#SortSam to produce a BAM (Binary Alignment/Map) file.
 
 {% include 'tutorial_expectations' %}
 
-In this tutorial, you will use the external program @md5sum@ to compute hashes instead of the built-in Python library used in earlier tutorials.
-
-Start by entering the @crunch_scripts@ directory of your Git working tree:
+Use the following command to create a new empty template using @arv pipeline_template create@:
 
 <notextile>
-<pre><code>~$ <span class="userinput">cd <b>you</b>/crunch_scripts</span>
-</code></pre>
+<pre><code>~$ <span class="userinput">arv edit $(arv --format=uuid pipeline_template create --pipeline-template '{}') name components </span></code></pre>
 </notextile>
 
-Next, using @nano@ or your favorite Unix text editor, create a new file called @run-md5sum.py@ in the @crunch_scripts@ directory.
-
-notextile. <pre>~/<b>you</b>/crunch_scripts$ <code class="userinput">nano run-md5sum.py</code></pre>
+* @--format=uuid@ option prints out just the unique identifier for the new template, instead of the entire template record (default)
 
-Add the following code to use the @md5sum@ program to compute the hash of each file in a collection:
+This will open the template record in an interactive text editor (as specified by $EDITOR or $VISUAL, otherwise defaults to @nano@) using @arv edit@.  Now add the following content:
 
-<notextile> {% code 'run_md5sum_py' as python %} </notextile>
+<notextile>{% code 'tutorial_bwa_sortsam_pipeline' as javascript %}</notextile>
 
-Make the file executable:
+* @"name"@ is a human-readable name for the pipeline.
+* @"components"@ is a set of scripts or commands that make up the pipeline.  Each component is given an identifier (@"bwa-mem"@ and @"SortSam"@) in this example).
+** Each entry in components @"components"@ is an Arvados job submission.  For more information about individual jobs, see the "job object reference":{{site.baseurl}}/api/schema/Job.html and "job create method.":{{site.baseurl}}/api/methods/jobs.html#create
+* @"repository"@, @"script_version"@, and @"script"@ indicate that we intend to use the external @"run-command"@ tool wrapper that is part of the Arvados.  These parameters are described in more detail in "Writing a script":tutorial-firstscript.html
+* @"output_is_persistent"@ indicates whether the output of the component is considered valuable. If this value is false (or not given), the output will be treated as intermediate data which may be eventually deleted to reclaim disk space.
+* @"runtime_constraints"@ describes runtime resource requirements for the component.
+** @"docker_image"@ specifies the "Docker":https://www.docker.com/ runtime environment in which to run the job.  The Docker image @"arvados/jobs-java-bwa-samtools"@ supplied here has the Arvados SDK, Java runtime environment, bwa, and samtools installed.
+* @"script_parameters"@ describes the component parameters.
+** @"command"@ is the actual command line to invoke the @bwa@ and then @SortSam@.  The notation @$()@ denotes macro substitution commands evaluated by the run-command tool wrapper.
+** @"stdout"@ indicates that the output of this command should be captured to a file.
+** @$(node.cores)@ evaluates to the number of cores available on the compute node at time the command is run.
+** @$(tmpdir)@ evaluates to the local path for temporary directory the command should use for scratch data.
+** @$(reference_collection)@ evaluates to the script_parameter @"reference_collection"@
+** @$(dir $(...))@ constructs a local path to a directory representing the supplied Arvados collection.
+** @$(file $(...))@ constructs a local path to a given file within the supplied Arvados collection.
+** @$(glob $(...))@ searches the specified path based on a file glob pattern and evalutes to the first result.
+** @$(basename $(...))@ evaluates to the supplied path with leading path portion and trailing filename extensions stripped
+** @"output_of"@ indicates that the @output@ of the @bwa-mem@ component should be used as the @"input"@ of @SortSam@.  Arvados uses these dependencies between components to automatically determine the correct order to run them.
 
-notextile. <pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x run-md5sum.py</span></code></pre>
+When using @run-command@, the tool should write its output to the current working directory.  The output will be automatically uploaded to Keep when the job completes.
 
-Next, use Git to stage the file, commit, and push:
+h2. Running your pipeline
 
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">git add run-md5sum.py</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git commit -m"run external md5sum program"</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git push origin master</span>
-</code></pre>
-</notextile>
+Your new pipeline template should appear at the top of the Workbench "pipeline&nbsp;templates":https://{{ site.arvados_workbench_host }}/pipeline_templates page.  You can run your pipeline "using Workbench":tutorial-pipeline-workbench.html or the "command line.":{{site.baseurl}}/user/topics/running-pipeline-command-line.html
 
-You should now be able to run your new script using Crunch, with @"script"@ referring to our new @run-md5sum.py@ script.
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;~/the_pipeline &lt;&lt;EOF
-{
-  "name":"Run external md5sum program",
-  "components":{
-    "do_hash":{
-      "script":"run-md5sum.py",
-      "script_parameters":{
-        "input":{
-          "required": true,
-          "dataclass": "Collection"
-        }
-      },
-      "repository":"$USER",
-      "script_version":"master"
-    }
-  }
-}
-EOF
-</span>~/<b>you</b>/crunch_scripts$ <span class="userinput">arv pipeline_template create --pipeline-template "$(cat ~/the_pipeline)"</span>
-</code></pre>
-</notextile>
+Test data is available in the "Arvados Tutorial":https://{{ site.arvados_workbench_host }}/projects/qr1hi-j7d0g-u7zg1qdaowykd8d project:
 
-(Your shell should automatically fill in @$USER@ with your login name.  The JSON that gets saved should have @"repository"@ pointed at your personal Git repository.)
+* Choose <i class="fa fa-fw fa-archive"></i> "Tutorial chromosome 19 reference (2463fa9efeb75e099685528b3b9071e0+438)":https://{{ site.arvados_workbench_host }}/collections/2463fa9efeb75e099685528b3b9071e0+438 for the "reference_collection" parameter
+* Choose <i class="fa fa-fw fa-archive"></i> "Tutorial sample exome (3229739b505d2b878b62aed09895a55a+142)":https://{{ site.arvados_workbench_host }}/collections/3229739b505d2b878b62aed09895a55a+142 for the "sample" parameter
 
-Your new pipeline template will appear on the Workbench "Pipeline&nbsp;templates":https://{{ site.arvados_workbench_host }}/pipeline_templates page.  You can run the "pipeline using Workbench":tutorial-pipeline-workbench.html.
+For more information and examples for writing pipelines, see the "pipeline template reference":{{site.baseurl}}/api/schema/PipelineTemplate.html
index 476fdf2a09120ae0d7fb081bc07423cecdea36c1..b045d624b873195b955c69fdf9e812db35ba68fb 100644 (file)
@@ -2,58 +2,25 @@
 layout: default
 navsection: userguide
 navmenu: Tutorials
-title: "Writing a pipeline"
+title: "Writing a Crunch script"
 ...
 
-In this tutorial, we will write the "hash" script demonstrated in the first tutorial.
+This tutorial demonstrates how to write a script using Arvados Python SDK.  The Arvados SDK supports access to advanced features not available using the @run-command@ wrapper, such as scheduling concurrent tasks across nodes.
 
 {% include 'tutorial_expectations' %}
 
-This tutorial uses *@you@* to denote your username.  Replace *@you@* with your user name in all the following examples.
+This tutorial uses @$USER@ to denote your username.  Replace @$USER@ with your user name in all the following examples.
 
-h2. Setting up Git
-
-All Crunch scripts are managed through the Git revision control system.  Before you start using Git, you should do some basic configuration (you only need to do this the first time):
-
-<notextile>
-<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
-~$ <span class="userinput">git config --global user.email <b>you</b>@example.com</span></code></pre>
-</notextile>
-
-On the Arvados Workbench, navigate to "Code repositories":https://{{site.arvados_workbench_host}}/repositories.  You should see a repository with your user name listed in the *name* column.  Next to *name* is the column *push_url*.  Copy the *push_url* value associated with your repository.  This should look like <notextile><code>git@git.{{ site.arvados_api_host }}:<b>you</b>.git</code></notextile>.
-
-Next, on the Arvados virtual machine, clone your Git repository:
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd $HOME</span> # (or wherever you want to install)
-~$ <span class="userinput">git clone git@git.{{ site.arvados_api_host }}:<b>you</b>.git</span>
-Cloning into '<b>you</b>'...</code></pre>
-</notextile>
-
-This will create a Git repository in the directory called *@you@* in your home directory. Say yes when prompted to continue with connection.
-Ignore any warning that you are cloning an empty repository.
-
-{% include 'notebox_begin' %}
-For more information about using Git, try
-
-notextile. <pre><code>$ <span class="userinput">man gittutorial</span></code></pre>
-
-or *"search Google for Git tutorials":http://google.com/#q=git+tutorial*.
-{% include 'notebox_end' %}
-
-h2. Creating a Crunch script
-
-Start by entering the *@you@* directory created by @git clone@.  Next create a subdirectory called @crunch_scripts@ and change to that directory:
+Start by creating a directory called @$USER@ .  Next, create a subdirectory called @crunch_scripts@ and change to that directory:
 
 <notextile>
-<pre><code>~$ <span class="userinput">cd <b>you</b></span>
-~/<b>you</b>$ <span class="userinput">mkdir crunch_scripts</span>
-~/<b>you</b>$ <span class="userinput">cd crunch_scripts</span></code></pre>
+<pre><code>~$ <span class="userinput">mkdir -p tutorial/crunch_scripts</span>
+~$ <span class="userinput">cd tutorial/crunch_scripts</span></code></pre>
 </notextile>
 
 Next, using @nano@ or your favorite Unix text editor, create a new file called @hash.py@ in the @crunch_scripts@ directory.
 
-notextile. <pre>~/<b>you</b>/crunch_scripts$ <code class="userinput">nano hash.py</code></pre>
+notextile. <pre>~/tutorial/crunch_scripts$ <code class="userinput">nano hash.py</code></pre>
 
 Add the following code to compute the MD5 hash of each file in a collection:
 
@@ -61,85 +28,74 @@ Add the following code to compute the MD5 hash of each file in a collection:
 
 Make the file executable:
 
-notextile. <pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x hash.py</span></code></pre>
-
-{% include 'notebox_begin' %}
-The steps below describe how to execute the script after committing changes to Git. To run a script locally for testing, please see "debugging a crunch script":{{site.baseurl}}/user/topics/tutorial-job-debug.html.
+notextile. <pre><code>~/tutorial/crunch_scripts$ <span class="userinput">chmod +x hash.py</span></code></pre>
 
-{% include 'notebox_end' %}
-
-Next, add the file to the staging area.  This tells @git@ that the file should be included on the next commit.
-
-notextile. <pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">git add hash.py</span></code></pre>
-
-Next, commit your changes.  All staged changes are recorded into the local git repository:
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">git commit -m"my first script"</span>
-[master (root-commit) 27fd88b] my first script
- 1 file changed, 45 insertions(+)
- create mode 100755 crunch_scripts/hash.py</code></pre>
-</notextile>
-
-Finally, upload your changes to the Arvados server:
+Next, create a submission job record.  This describes a specific invocation of your script:
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">git push origin master</span>
-Counting objects: 4, done.
-Compressing objects: 100% (2/2), done.
-Writing objects: 100% (4/4), 682 bytes, done.
-Total 4 (delta 0), reused 0 (delta 0)
-To git@git.qr1hi.arvadosapi.com:<b>you</b>.git
- * [new branch]      master -> master</code></pre>
+<pre><code>~/tutorial/crunch_scripts$ <span class="userinput">cat &gt;~/the_job &lt;&lt;EOF
+{
+ "repository":"",
+ "script":"hash.py",
+ "script_version":"$HOME/tutorial",
+ "script_parameters":{
+   "input":"c1bad4b39ca5a924e481008009d94e32+210"
+ }
+}
+EOF</span>
+</code></pre>
 </notextile>
 
-h2. Create a pipeline template
-
-Next, create a file that contains the pipeline definition:
+You can now run your script on your local workstation or VM using @arv-crunch-job@:
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cd ~</span>
-~$ <span class="userinput">cat &gt;the_pipeline &lt;&lt;EOF
-{
-  "name":"My first pipeline",
-  "components":{
-    "do_hash":{
-      "script":"hash.py",
-      "script_parameters":{
-        "input":{
-          "required": true,
-          "dataclass": "Collection"
-        }
-      },
-      "repository":"$USER",
-      "script_version":"master",
-      "output_is_persistent":true
-    }
-  }
-}
-EOF
-</span></code></pre>
+<pre><code>~/tutorial/crunch_scripts</span>$ <span class="userinput">arv-crunch-job --job "$(cat ~/the_job)"</span>
+2014-08-06_15:16:22 qr1hi-8i9sb-qyrat80ef927lam 14473  check slurm allocation
+2014-08-06_15:16:22 qr1hi-8i9sb-qyrat80ef927lam 14473  node localhost - 1 slots
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  start
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  script hash.py
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  script_version /home/peter/peter
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  script_parameters {"input":"c1bad4b39ca5a924e481008009d94e32+210"}
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  runtime_constraints {"max_tasks_per_node":0}
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  start level 0
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 0 done, 0 running, 1 todo
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473 0 job_task qr1hi-ot0gb-lptn85mwkrn9pqo
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473 0 child 14478 started on localhost.1
+2014-08-06_15:16:23 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 0 done, 1 running, 0 todo
+2014-08-06_15:16:24 qr1hi-8i9sb-qyrat80ef927lam 14473 0 stderr crunchstat: Running [stdbuf --output=0 --error=0 /home/$USER/tutorial/crunch_scripts/hash.py]
+2014-08-06_15:16:24 qr1hi-8i9sb-qyrat80ef927lam 14473 0 child 14478 on localhost.1 exit 0 signal 0 success=true
+2014-08-06_15:16:24 qr1hi-8i9sb-qyrat80ef927lam 14473 0 success in 1 seconds
+2014-08-06_15:16:24 qr1hi-8i9sb-qyrat80ef927lam 14473 0 output
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473  wait for last 0 children to finish
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 1 done, 0 running, 1 todo
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473  start level 1
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 1 done, 0 running, 1 todo
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473 1 job_task qr1hi-ot0gb-e3obm0lv6k6p56a
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473 1 child 14504 started on localhost.1
+2014-08-06_15:16:25 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 1 done, 1 running, 0 todo
+2014-08-06_15:16:26 qr1hi-8i9sb-qyrat80ef927lam 14473 1 stderr crunchstat: Running [stdbuf --output=0 --error=0 /home/$USER/tutorial/crunch_scripts/hash.py]
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473 1 child 14504 on localhost.1 exit 0 signal 0 success=true
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473 1 success in 10 seconds
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473 1 output 50cafdb29cc21dd6eaec85ba9e0c6134+56+Aef0f991b80fa0b75f802e58e70b207aa184d24ff@53f4bbd3
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473  wait for last 0 children to finish
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473  status: 2 done, 0 running, 0 todo
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473  Freeze not implemented
+2014-08-06_15:16:35 qr1hi-8i9sb-qyrat80ef927lam 14473  collate
+2014-08-06_15:16:36 qr1hi-8i9sb-qyrat80ef927lam 14473  output d6338df28d6b8e5d14929833b417e20e+107+Adf1ce81222b6992ce5d33d8bfb28a6b5a1497898@53f4bbd4
+2014-08-06_15:16:37 qr1hi-8i9sb-qyrat80ef927lam 14473  finish
+2014-08-06_15:16:38 qr1hi-8i9sb-qyrat80ef927lam 14473  log manifest is 7fe8cf1d45d438a3ca3ac4a184b7aff4+83
+</code></pre>
 </notextile>
 
-* @cat@ is a standard Unix utility that writes a sequence of input to standard output.
-* @<<EOF@ tells the shell to direct the following lines into the standard input for @cat@ up until it sees the line @EOF@.
-* @>the_pipeline@ redirects standard output to a file called @the_pipeline@.
-* @"name"@ is a human-readable name for the pipeline.
-* @"components"@ is a set of scripts that make up the pipeline.
-* The component is listed with a human-readable name (@"do_hash"@ in this example).
-* @"repository"@ is the name of a git repository to search for the script version.  You can access a list of available git repositories on the Arvados Workbench under "Code repositories":https://{{site.arvados_workbench_host}}/repositories.  Your shell should automatically fill in @$USER@ with your login name, so that the final JSON has @"repository"@ pointed at your personal Git repository.
-* @"script_version"@ specifies the version of the script that you wish to run.  This can be in the form of an explicit Git revision hash, a tag, or a branch (in which case it will use the HEAD of the specified branch).  Arvados logs the script version that was used in the run, enabling you to go back and re-run any past job with the guarantee that the exact same code will be used as was used in the previous run.
-* @"script"@ specifies the filename of the script to run.  Crunch expects to find this in the @crunch_scripts/@ subdirectory of the Git repository.
-* @"script_parameters"@ describes the parameters for the script.  In this example, there is one parameter called @input@ which is @required@ and is a @Collection@.
-* @"output_is_persistent"@ indicates whether the output of the job is considered valuable. If this value is false (or not given), the output will be treated as intermediate data and eventually deleted to reclaim disk space.
-
-Now, use @arv pipeline_template create@ to register your pipeline template in Arvados:
+Although the job runs locally, the output of the job has been saved to Keep, the Arvados file store.  The "output" line (third from the bottom) provides the "Keep locator":{{site.baseurl}}/user/tutorials/tutorial-keep-get.html to which the script's output has been saved.  Copy the output identifier and use @arv-ls@ to list the contents of your output collection, and @arv-get@ to download it to the current directory:
 
 <notextile>
-<pre><code>~$ <span class="userinput">arv pipeline_template create --pipeline-template "$(cat the_pipeline)"</span>
+<pre><code>~/tutorial/crunch_scripts$ <span class="userinput">arv-ls d6338df28d6b8e5d14929833b417e20e+107+Adf1ce81222b6992ce5d33d8bfb28a6b5a1497898@53f4bbd4</span>
+./md5sum.txt
+~/tutorial/crunch_scripts$ <span class="userinput">arv-get d6338df28d6b8e5d14929833b417e20e+107+Adf1ce81222b6992ce5d33d8bfb28a6b5a1497898@53f4bbd4/ .</span>
+~/tutorial/crunch_scripts$ <span class="userinput">cat md5sum.txt</span>
+44b8ae3fde7a8a88d2f7ebd237625b4f c1bad4b39ca5a924e481008009d94e32+210/./var-GS000016015-ASM.tsv.bz2
 </code></pre>
 </notextile>
 
-Your new pipeline template will appear on the Workbench "Pipeline&nbsp;templates":https://{{ site.arvados_workbench_host }}/pipeline_templates page.  You can run the "pipeline using Workbench":tutorial-pipeline-workbench.html.
-
-For more information and examples for writing pipelines, see the "pipeline template reference":{{site.baseurl}}/api/schema/PipelineTemplate.html
+Running locally is convenient for development and debugging, as it permits a fast iterative development cycle.  Your job run is also recorded by Arvados, and will show up in the "Recent jobs and pipelines" panel on the "Workbench dashboard":https://{{site.arvados_workbench_host}}.  This provides limited provenance, by recording the input parameters, the execution log, and the output.  However, running locally does not allow you to scale out to multiple nodes, and does not store the complete system snapshot required to achieve reproducibility; to that you need to "submit a job to the Arvados cluster":{{site.baseurl}}/user/tutorials/tutorial-submit-job.html
diff --git a/doc/user/tutorials/tutorial-keep-get.html.textile.liquid b/doc/user/tutorials/tutorial-keep-get.html.textile.liquid
new file mode 100644 (file)
index 0000000..8f991d9
--- /dev/null
@@ -0,0 +1,47 @@
+---
+layout: default
+navsection: userguide
+title: "Downloading data"
+...
+
+This tutorial describes how to list and download Arvados data collections using the command line tools @arv-ls@ and @arv-get@.  It is also possible to download files from a collection from the Workbench page for the collection, covered in "running a pipeline using Workbench":{{site.baseurl}}/user/tutorials/tutorial-pipeline-workbench.html
+
+{% include 'tutorial_expectations' %}
+
+You can view the contents of a collection using @arv-ls@:
+
+<notextile>
+<pre><code>~$ <span class="userinput">arv-ls c1bad4b39ca5a924e481008009d94e32+210</span>
+var-GS000016015-ASM.tsv.bz2
+</code></pre>
+
+<pre><code>~$ <span class="userinput">arv-ls 887cd41e9c613463eab2f0d885c6dd96+83</span>
+alice.txt
+bob.txt
+carol.txt
+</code></pre>
+</notextile>
+
+Use @-s@ to print file sizes rounded up to the nearest kilobyte:
+
+<notextile>
+<pre><code>~$ <span class="userinput">arv-ls -s c1bad4b39ca5a924e481008009d94e32+210</span>
+221887 var-GS000016015-ASM.tsv.bz2
+</code></pre>
+</notextile>
+
+Use @arv-get@ to download the contents of a collection and place it in the directory specified in the second argument (in this example, @.@ for the current directory):
+
+<notextile>
+<pre><code>~$ <span class="userinput">arv-get c1bad4b39ca5a924e481008009d94e32+210/ .</span>
+~$ <span class="userinput">ls var-GS000016015-ASM.tsv.bz2</span>
+var-GS000016015-ASM.tsv.bz2
+</code></pre>
+</notextile>
+
+You can also download individual files:
+
+<notextile>
+<pre><code>~$ <span class="userinput">arv-get 887cd41e9c613463eab2f0d885c6dd96+83/alice.txt .</span>
+</code></pre>
+</notextile>
diff --git a/doc/user/tutorials/tutorial-keep-mount.html.textile.liquid b/doc/user/tutorials/tutorial-keep-mount.html.textile.liquid
new file mode 100644 (file)
index 0000000..e07be5d
--- /dev/null
@@ -0,0 +1,32 @@
+---
+layout: default
+navsection: userguide
+title: "Mounting Keep as a filesystem"
+...
+
+This tutoral describes how to access Arvados collections using traditional filesystem tools by mounting Keep as a read-only file system using @arv-mount@.
+
+{% include 'tutorial_expectations' %}
+
+h2. Arv-mount
+
+@arv-mount@ provides several features:
+
+* You can browse, open and read Keep entries as if they are regular files.
+* It is easy for existing tools to access files in Keep.
+* Data is downloaded on demand.  It is not necessary to download an entire file or collection to start processing.
+
+The default mode permits browsing any collection in Arvados as a subdirectory under the mount directory.  To avoid having to fetch a potentially large list of all collections, collection directories only come into existence when explicitly accessed by their keep locator.
+
+<notextile>
+<pre><code>~$ <span class="userinput">mkdir -p keep</span>
+~$ <span class="userinput">arv-mount keep</span>
+~$ <span class="userinput">cd keep/c1bad4b39ca5a924e481008009d94e32+210</span>
+~/keep/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">ls</span>
+var-GS000016015-ASM.tsv.bz2
+~/keep/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">md5sum var-GS000016015-ASM.tsv.bz2</span>
+44b8ae3fde7a8a88d2f7ebd237625b4f  var-GS000016015-ASM.tsv.bz2
+~/keep/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">cd ../..</span>
+~$ <span class="userinput">fusermount -u keep</span>
+</code></pre>
+</notextile>
index 3546c5c10189476cdef72f9c8a136ed465f862a2..59ebe84ddbbffd21f219655de601a8b51ef4b8de 100644 (file)
 ---
 layout: default
 navsection: userguide
-title: "Storing and Retrieving data using Keep"
+title: "Uploading data"
 ...
 
-This tutorial introduces you to the Arvados file storage system.
+This tutorial describes how to to upload new Arvados data collections using the command line tool @arv-put@.  This example uses a freely available TSV file containing variant annotations from "Personal Genome Project (PGP)":http://www.pgp-hms.org participant "hu599905.":https://my.pgp-hms.org/profile/hu599905
 
-{% include 'tutorial_expectations' %}
-
-The Arvados distributed file system is called *Keep*.  Keep is a content-addressable file system.  This means that files are managed using special unique identifiers derived from the _contents_ of the file, rather than human-assigned file names (specifically, the MD5 hash).  This has a number of advantages:
-* Files can be stored and replicated across a cluster of servers without requiring a central name server.
-* Both the server and client systematically validate data integrity because the checksum is built into the identifier.
-* Data duplication is minimized—two files with the same contents will have in the same identifier, and will not be stored twice.
-* It avoids data race conditions, since an identifier always points to the same data.
-
-h1. Putting Data into Keep
-
-We will start by downloading a freely available VCF file from "Personal Genome Project (PGP)":http://www.personalgenomes.org subject "hu599905":https://my.personalgenomes.org/profile/hu599905 to a staging directory on the VM, and adding it to Keep.  In the following commands, replace *@you@* with your login name.
-
-First, log into your Arvados VM and set up the staging area:
-
-notextile. <pre><code>~$ <span class="userinput">mkdir /scratch/<b>you</b></span></code></pre>
-
-Next, download the file:
+notextile. <div class="spaced-out">
 
+# Begin by installing the "Arvados Python SDK":{{site.baseurl}}/sdk/python/sdk-python.html on the system from which you will upload the data (such as your workstation, or a server containing data from your sequencer).  This will install the Arvados file upload tool, @arv-put@.  Alternately, you can log into an Arvados VM (instructions for "Unix":{{site.baseurl}}/user/getting_started/ssh-access-unix.html#login or "Windows":{{site.baseurl}}/user/getting_started/ssh-access-windows.html#login).
+# On system from which you will upload data, configure the environment with the Arvados instance host name and authentication token as decribed in "Getting an API token.":{{site.baseurl}}/user/reference/api-tokens.html  (If you are logged into an Arvados VM, you can skip this step.)
+# Download the following example file.  (If you are uploading your own data, you can skip this step.)
 <notextile>
-<pre><code>~$ <span class="userinput">cd /scratch/<b>you</b></span>
-/scratch/<b>you</b>$ <span class="userinput">curl -o var-GS000016015-ASM.tsv.bz2 'https://warehouse.personalgenomes.org/warehouse/f815ec01d5d2f11cb12874ab2ed50daa+234+K@ant/var-GS000016015-ASM.tsv.bz2'</span>
+<pre><code>~$ <span class="userinput">curl -o var-GS000016015-ASM.tsv.bz2 'https://warehouse.pgp-hms.org/warehouse/f815ec01d5d2f11cb12874ab2ed50daa+234+K@ant/var-GS000016015-ASM.tsv.bz2'</span>
   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                  Dload  Upload   Total   Spent    Left  Speed
 100  216M  100  216M    0     0  10.0M      0  0:00:21  0:00:21 --:--:-- 9361k
 </code></pre>
 </notextile>
-
-{% include 'notebox_begin' %}
-
-If you have your own data, for example @MyData.vcf@, you can use @scp@ or @rsync@ to copy from your local workstation to the shell VM (run this on your local workstation):
-
-notextile. <pre><code>~$ <span class="userinput">scp MyData.vcf <b>you@shell</b>.arvados:/scratch/<b>you</b>/MyData.vcf</span></code></pre>
-
-{% include 'notebox_end' %}
-
-Now use @arv keep put@ to add your VCF data to Keep, then delete the local copy of the file:
-
+# Now upload the file to Keep using @arv-put@:
 <notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep put var-GS000016015-ASM.tsv.bz2</span>
+<pre><code>~$ <span class="userinput">arv-put var-GS000016015-ASM.tsv.bz2</span>
+216M / 216M 100.0%
 c1bad4b39ca5a924e481008009d94e32+210
-/scratch/<b>you</b>$ <span class="userinput">rm var-GS000016015-ASM.tsv.bz2</span>
 </code></pre>
 </notextile>
 
-The output value @c1bad4b39ca5a924e481008009d94e32+210@ from @arv keep put@ is the Keep locator.  This enables you to access the file you just uploaded, and is explained in the next section.
+* The output value @c1bad4b39ca5a924e481008009d94e32+210@ is the Arvados collection locator that uniquely describes this file.
 
-h2(#dir). Putting a directory
+Now go to the workbench collections page: <a href="https://{{ site.arvados_workbench_host }}/collections" target="_blank">https://{{ site.arvados_workbench_host }}/collections</a>.  Your newly uploaded collection should appear near the top, with the value in the *uuid* column matching the Arvados collection locator that was printed by @arv-put@.  Click on the *<i class="fa fa-fw fa-archive"></i> Show* button to go to the workbench page for your collection.  Alternately, you can paste the Arvados collection locator into the *Search* box of the collections page to find your collection.
 
-You can also use @arv keep put@ to add an entire directory:
+The show collection page allows you to view the contents of the collection, download files from the collection, and set sharing options.  To put your collection into a project, click on  <span class="btn btn-xs btn-primary" ><i class="fa fa-fw fa-folder"></i> Add to project...</span>.  This will open a modal dialog allowing you to select a destination project for your collection.
 
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">mkdir tmp</span>
-/scratch/<b>you</b>$ <span class="userinput">echo "hello alice" > tmp/alice.txt</span>
-/scratch/<b>you</b>$ <span class="userinput">echo "hello bob" > tmp/bob.txt</span>
-/scratch/<b>you</b>$ <span class="userinput">echo "hello carol" > tmp/carol.txt</span>
-/scratch/<b>you</b>$ <span class="userinput">arv keep put tmp</span>
-0M / 0M 100.0%
-887cd41e9c613463eab2f0d885c6dd96+83
-</code></pre>
-</notextile>
-
-The locator @887cd41e9c613463eab2f0d885c6dd96+83@ represents a collection with multiple files.
-
-h1. Getting Data from Keep
-
-h2. Using Workbench
-
-You may access collections through the "Collections section of Arvados Workbench":https://{{ site.arvados_workbench_host }}/collections at *Data* %(rarr)&rarr;% *Collections (data files)*.  You can also access individual files within a collection.  Some examples:
-
-* "https://{{ site.arvados_workbench_host }}/collections/c1bad4b39ca5a924e481008009d94e32+210":https://{{ site.arvados_workbench_host }}/collections/c1bad4b39ca5a924e481008009d94e32+210
-* "https://{{ site.arvados_workbench_host }}/collections/887cd41e9c613463eab2f0d885c6dd96+83/alice.txt":https://{{ site.arvados_workbench_host }}/collections/887cd41e9c613463eab2f0d885c6dd96+83/alice.txt
-
-h2(#arv-get). Using the command line
-
-You can view the contents of a collection using @arv keep ls@:
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep ls c1bad4b39ca5a924e481008009d94e32+210</span>
-var-GS000016015-ASM.tsv.bz2
-</code></pre>
-
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep ls 887cd41e9c613463eab2f0d885c6dd96+83</span>
-alice.txt
-bob.txt
-carol.txt
-</code></pre>
-</notextile>
+notextile. </div>
 
-Use @-s@ to print file sizes rounded up to the nearest kilobyte:
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep ls -s c1bad4b39ca5a924e481008009d94e32+210</span>
-221887 var-GS000016015-ASM.tsv.bz2
-</code></pre>
-</notextile>
-
-Use @arv keep get@ to download the contents of a collection and place it in the directory specified in the second argument (in this example, @.@ for the current directory):
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep get c1bad4b39ca5a924e481008009d94e32+210/ .</span>
-/scratch/<b>you</b>$ <span class="userinput">ls var-GS000016015-ASM.tsv.bz2</span>
-var-GS000016015-ASM.tsv.bz2
-</code></pre>
-</notextile>
-
-You can also download individual files:
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">arv keep get 887cd41e9c613463eab2f0d885c6dd96+83/alice.txt .</span>
-</code></pre>
-</notextile>
-
-With a local copy of the file, we can do some computation, for example computing the MD5 hash of the complete file:
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">md5sum var-GS000016015-ASM.tsv.bz2</span>
-44b8ae3fde7a8a88d2f7ebd237625b4f  var-GS000016015-ASM.tsv.bz2
-</code></pre>
-</notextile>
-
-h2. Using arv-mount
-
-Use @arv-mount@ to mount a Keep collection and access it using traditional filesystem tools.
-
-<notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">mkdir -p mnt</span>
-/scratch/<b>you</b>$ <span class="userinput">arv-mount --collection c1bad4b39ca5a924e481008009d94e32+210 mnt &</span>
-/scratch/<b>you</b>$ <span class="userinput">cd mnt</span>
-/scratch/<b>you</b>/mnt$ <span class="userinput">ls</span>
-var-GS000016015-ASM.tsv.bz2
-/scratch/<b>you</b>/mnt$ <span class="userinput">md5sum var-GS000016015-ASM.tsv.bz2</span>
-44b8ae3fde7a8a88d2f7ebd237625b4f  var-GS000016015-ASM.tsv.bz2
-/scratch/<b>you</b>/mnt$ <span class="userinput">cd ..</span>
-/scratch/<b>you</b>$ <span class="userinput">fusermount -u mnt</span>
-</code></pre>
-</notextile>
+h2(#dir). Putting a directory
 
-You can also mount the entire Keep namespace in "magic directory" mode:
+If you give @arv-put@ a directory, it will recursively upload the entire directory:
 
 <notextile>
-<pre><code>/scratch/<b>you</b>$ <span class="userinput">mkdir -p mnt</span>
-/scratch/<b>you</b>$ <span class="userinput">arv-mount mnt &</span>
-/scratch/<b>you</b>$ <span class="userinput">cd mnt/c1bad4b39ca5a924e481008009d94e32+210</span>
-/scratch/<b>you</b>/mnt/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">ls</span>
-var-GS000016015-ASM.tsv.bz2
-/scratch/<b>you</b>/mnt/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">md5sum var-GS000016015-ASM.tsv.bz2</span>
-44b8ae3fde7a8a88d2f7ebd237625b4f  var-GS000016015-ASM.tsv.bz2
-/scratch/<b>you</b>/mnt/c1bad4b39ca5a924e481008009d94e32+210$ <span class="userinput">cd ../..</span>
-/scratch/<b>you</b>$ <span class="userinput">fusermount -u mnt</span>
+<pre><code>~$ <span class="userinput">mkdir tmp</span>
+~$ <span class="userinput">echo "hello alice" > tmp/alice.txt</span>
+~$ <span class="userinput">echo "hello bob" > tmp/bob.txt</span>
+~$ <span class="userinput">echo "hello carol" > tmp/carol.txt</span>
+~$ <span class="userinput">arv-put tmp</span>
+0M / 0M 100.0%
+887cd41e9c613463eab2f0d885c6dd96+83
 </code></pre>
 </notextile>
-
-@arv-mount@ provides several features:
-
-* You can browse, open and read Keep entries as if they are regular files.
-* It is easy for existing tools to access files in Keep.
-* Data is downloaded on demand.  It is not necessary to download an entire file or collection to start processing.
index e5bf3399e05516c04d8e8b79da21b00e1ae7af39..83303ad7cc18416f40b49c3119c1eee12ba302dd 100644 (file)
@@ -8,72 +8,19 @@ A pipeline in Arvados is a collection of crunch scripts, in which the output fro
 
 {% include 'tutorial_expectations' %}
 
-This tutorial uses *@you@* to denote your username.  Replace *@you@* with your user name in all the following examples.
 
-h2. Create a new script
-
-Our second script will filter the output of @hash.py@ and only include hashes that start with 0.  Create a new script in <notextile><code>~/<b>you</b>/crunch_scripts/</code></notextile> called @0-filter.py@:
-
-<notextile> {% code '0_filter_py' as python %} </notextile>
-
-Now add it to your repository:
+First, use @arv pipeline_template create@ to create a new empty template.  The @--format=uuid@ option will print out the unique identifier for the new template:
 
 <notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">chmod +x 0-filter.py</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git add 0-filter.py</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git commit -m"zero filter"</span>
-~/<b>you</b>/crunch_scripts$ <span class="userinput">git push origin master</span>
-</code></pre>
-</notextile>
-
-h2. Create a pipeline template
-
-Next, create a file that contains the pipeline definition:
-
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">cat &gt;~/the_pipeline &lt;&lt;EOF
-{
-  "name":"Filter MD5 hash values",
-  "components":{
-    "do_hash":{
-      "script":"hash.py",
-      "script_parameters":{
-        "input":{
-          "required": true,
-          "dataclass": "Collection"
-        }
-      },
-      "repository":"$USER",
-      "script_version":"master",
-      "output_is_persistent":false
-    },
-    "do_filter":{
-      "script":"0-filter.py",
-      "script_parameters":{
-        "input":{
-          "output_of":"do_hash"
-        }
-      },
-      "repository":"$USER",
-      "script_version":"master",
-      "output_is_persistent":true
-    }
-  }
-}
-EOF
+<pre><code>~$ <span class="userinput">arv --format=uuid pipeline_template create --pipeline-template '{}'</span>
+qr1hi-p5p6p-wt1vdhkezgx7g2k
 </span></code></pre>
 </notextile>
 
-* @"output_of"@ indicates that the @output@ of the @do_hash@ component should be used as the @"input"@ of @do_filter@.  Arvados uses these dependencies between jobs to automatically determine the correct order to run them.
-
-(Your shell should automatically fill in @$USER@ with your login name.  The JSON that gets saved should have @"repository"@ pointed at your personal Git repository.)
+Next, use @arv edit@ to edit the template.  This will open the template record in an interactive text editor (as specified by $EDITOR or $VISUAL, otherwise defaults to @nano@).  Replace the empty fields with the following content:
 
-Now, use @arv pipeline_template create@ to register your pipeline template in Arvados:
+<notextile>{% code 'tutorial_bwa_pipeline' as javascript %}</notextile>
 
-<notextile>
-<pre><code>~/<b>you</b>/crunch_scripts$ <span class="userinput">arv pipeline_template create --pipeline-template "$(cat ~/the_pipeline)"</span>
-</code></pre>
-</notextile>
 
 Your new pipeline template will appear on the Workbench "Pipeline&nbsp;templates":https://{{ site.arvados_workbench_host }}/pipeline_templates page.
 
index 5f98c8cbeec27bd312ab19fb2d59343d04f01800..d0372b71b7b2e5a37652c7ee6b7199c9e07c0277 100644 (file)
@@ -4,25 +4,29 @@ navsection: userguide
 title: "Running a pipeline using Workbench"
 ...
 
+A "pipeline" (sometimes called a "workflow" in other systems) is sequence of steps that apply various programs or tools to transform input data to output data.  Pipelines are the principal means of performing computation with Arvados.  This tutorial demonstrates how to run a single-stage pipeline to take a small data set of paired-end reads from an sample "exome":https://en.wikipedia.org/wiki/Exome in "FASTQ":https://en.wikipedia.org/wiki/FASTQ_format format and align them to "Chromosome 19":https://en.wikipedia.org/wiki/Chromosome_19_%28human%29 using the "bwa mem":http://bio-bwa.sourceforge.net/ tool, producing a "Sequence Alignment/Map (SAM)":https://samtools.github.io/ file.  This will introduce the following Arvados features:
+
+<div class="inside-list">
+* How to create a project.
+* How to browse available pipeline templates and create a new pipeline from an existing template.
+* How to browse and select input data for the pipeline and submit the pipeline to run on the Arvados cluster.
+* How to access your pipeline results.
+</div>
+
 notextile. <div class="spaced-out">
 
-# If you have not yet created any projects, first create a new project to save your work. Please see "Accessing Arvados Workbench":{{site.baseurl}}/user/getting_started/workbench.html page for details. If you already have created a project for your work, go to the project by clicking on the project in *My projects* panel.
-# Click on the <span class="btn btn-primary glyphicon-plus"> Add data...</span> button.
-# In the *Add data to project* popup window, enter *tutorial* in the search box.
-# The results should include a *tutorial* collection. 
-# Click on this collection and verify that it is the collection *var-GS000016015-ASM.tsv.bz2*.
-# Click on the <span class="btn btn-primary"> Add </span> button in the popup.  This adds the *var-GS000016015-ASM.tsv.bz2* collection to your project.
-# Click on the <span class="btn btn-primary glyphicon-gear"> Run a pipeline... </span> button
-# In the *Choose a pipeline to run* popup window, enter *tutorial* in the search box.
-# The result should include *Tutorial pipeline* template.
-# Click on this template and the click on <span class="btn btn-primary"> Next: choose inputs </span> button. This will take you to a new page to configure the pipeline instance.
-# In the *Inputs* tab, click on <span class="btn btn-primary"> Choose </span> button
-# In the *Choose a dataset* popup, click on the *tutorial* collection and click on <span class="btn btn-primary"> OK </span> button.
-# You can now run this pipeline by clicking on the <span class="btn btn-primary"> Run </span> button.
-# The page refreshes automatically every 15 seconds.  You should see the pipeline running, and then finish successfully.
-# Once the pipeline is finished, click on the *Show output files* button. This will take you to the collection page for the output of this pipeline.
-# Click on *md5sum.txt* to see the actual file that is the output of this pipeline.
-# Go back to the collection page for the result.  Click on the *Provenance graph* tab to see a graph illustrating the collections and scripts that were used to generate this file.
+# Start from the *Workbench Dashboard*.  You can return to the dashboard by clicking on *<i class="fa fa-lg fa-fw fa-home"></i> Home* in the upper left corner of any Workbench page.
+# Click on <span class="btn btn-sm btn-primary" > <i class="fa fa-fw fa-plus"></i> Add new project</span> on the "My projects" panel. This will direct you to the page for the new project.
+# On the new project page, click on the pencil icon <i class="fa fa-fw fa-pencil"></i> next to *New project* to pop up a text box and change the project title to *Tutorial output*.  Click on <span class="btn btn-xs btn-primary" ><i class="glyphicon glyphicon-ok"></i></span> to save the new name.
+# Click on <span class="btn btn-sm btn-primary"><i class="fa fa-fw fa-gear"></i> Run a pipeline...</span> This will open a modal dialog box titled *Choose a pipeline to run*.
+# Click on *<i class="fa fa-lg fa-fw fa-home"></i> Projects <span class="caret"></span>*.  Under *Projects shared with me* select *<i class="fa fa-fw fa-share-alt"></i> Arvados Tutorial*.
+# Select *<i class="fa fa-fw fa-gear"></i> Tutorial align using bwa mem* and click on <span class="btn btn-sm btn-primary" >Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i></span>.  This will load a new page where you will supply the inputs for the pipeline.
+# Click on <span class="btn btn-sm btn-primary" >Choose</span> under the first input parameter to the pipeline *reference_collection*.  This will open a modal dialog box titled *Choose a dataset*.
+# Once again click on *<i class="fa fa-lg fa-fw fa-home"></i> Projects <span class="caret"></span>* and under *Projects shared with me* select *<i class="fa fa-fw fa-share-alt"></i> Arvados Tutorial*.  Select *<i class="fa fa-fw fa-archive"></i> Tutorial chromosome 19 reference* and click on <span class="btn btn-sm btn-primary" >OK</span>.
+# Repeat the previous step to supply the *sample* parameter, this time choosing *<i class="fa fa-fw fa-archive"></i> Tutorial sample exome*.
+# Click on <span class="btn btn-sm btn-primary" >Run <i class="fa fa-fw fa-play"></i></span>.
+# This will refresh the page.  The pipeline will be queued and shortly start running.  You can track the progress by watching log messages from jobs.  This page refreshes automatically.  You will see <span class="label label-success">success</span> under the *job* the column when the pipeline completes successfully.
+# Click on *<i class="fa fa-fw fa-archive"></i> Show output files* to see the results of the job.  This will load a new page listing the output files from this pipeline.  Under the *Files* tab will be the output SAM file from the alignment tool
+# Click on the download icon <span class="btn btn-sm btn-info"><i class="fa fa-download"></i></span> to the right of the SAM file download your results.
 
 notextile. </div>
-
diff --git a/doc/user/tutorials/tutorial-submit-job.html.textile.liquid b/doc/user/tutorials/tutorial-submit-job.html.textile.liquid
new file mode 100644 (file)
index 0000000..8be72a2
--- /dev/null
@@ -0,0 +1,137 @@
+---
+layout: default
+navsection: userguide
+navmenu: Tutorials
+title: "Running on an Arvados cluster"
+...
+
+This tutorial demonstrates how to create a pipeline to run your crunch script on an Arvados cluster.  Cluster jobs can scale out to multiple nodes, and use @git@ and @docker@ to store the complete system snapshot required to achieve reproducibilty.
+
+{% include 'tutorial_expectations' %}
+
+This tutorial uses @$USER@ to denote your username.  Replace @$USER@ with your user name in all the following examples.
+
+h2. Setting up Git
+
+All Crunch scripts are managed through the Git revision control system.  Before you start using Git, you should do some basic configuration (you only need to do this the first time):
+
+<notextile>
+<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
+~$ <span class="userinput">git config --global user.email $USER@example.com</span></code></pre>
+</notextile>
+
+On the Arvados Workbench, navigate to "Code repositories":https://{{site.arvados_workbench_host}}/repositories.  You should see a repository with your user name listed in the *name* column.  Next to *name* is the column *push_url*.  Copy the *push_url* value associated with your repository.  This should look like <notextile><code>git@git.{{ site.arvados_api_host }}:$USER.git</code></notextile>.
+
+Next, on the Arvados virtual machine, clone your Git repository:
+
+<notextile>
+<pre><code>~$ <span class="userinput">cd $HOME</span> # (or wherever you want to install)
+~$ <span class="userinput">git clone git@git.{{ site.arvados_api_host }}:$USER.git</span>
+Cloning into '$USER'...</code></pre>
+</notextile>
+
+This will create a Git repository in the directory called @$USER@ in your home directory. Say yes when prompted to continue with connection.
+Ignore any warning that you are cloning an empty repository.
+
+{% include 'notebox_begin' %}
+For more information about using Git, try
+
+notextile. <pre><code>$ <span class="userinput">man gittutorial</span></code></pre>
+
+or *"search Google for Git tutorials":http://google.com/#q=git+tutorial*.
+{% include 'notebox_end' %}
+
+h2. Creating a Crunch script
+
+Start by entering the @$USER@ directory created by @git clone@.  Next create a subdirectory called @crunch_scripts@ and change to that directory:
+
+<notextile>
+<pre><code>~$ <span class="userinput">cd $USER</span>
+~/$USER$ <span class="userinput">mkdir crunch_scripts</span>
+~/$USER$ <span class="userinput">cd crunch_scripts</span></code></pre>
+</notextile>
+
+Next, using @nano@ or your favorite Unix text editor, create a new file called @hash.py@ in the @crunch_scripts@ directory.
+
+notextile. <pre>~/$USER/crunch_scripts$ <code class="userinput">nano hash.py</code></pre>
+
+Add the following code to compute the MD5 hash of each file in a collection (if you already completed "Writing a Crunch script":tutorial-firstscript.html you can just copy the @hash.py@ file you created previously.)
+
+<notextile> {% code 'tutorial_hash_script_py' as python %} </notextile>
+
+Make the file executable:
+
+notextile. <pre><code>~/$USER/crunch_scripts$ <span class="userinput">chmod +x hash.py</span></code></pre>
+
+Next, add the file to the staging area.  This tells @git@ that the file should be included on the next commit.
+
+notextile. <pre><code>~/$USER/crunch_scripts$ <span class="userinput">git add hash.py</span></code></pre>
+
+Next, commit your changes.  All staged changes are recorded into the local git repository:
+
+<notextile>
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">git commit -m"my first script"</span>
+[master (root-commit) 27fd88b] my first script
+ 1 file changed, 45 insertions(+)
+ create mode 100755 crunch_scripts/hash.py</code></pre>
+</notextile>
+
+Finally, upload your changes to the Arvados server:
+
+<notextile>
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">git push origin master</span>
+Counting objects: 4, done.
+Compressing objects: 100% (2/2), done.
+Writing objects: 100% (4/4), 682 bytes, done.
+Total 4 (delta 0), reused 0 (delta 0)
+To git@git.qr1hi.arvadosapi.com:$USER.git
+ * [new branch]      master -> master</code></pre>
+</notextile>
+
+h2. Create a pipeline template
+
+Next, create a file that contains the pipeline definition:
+
+<notextile>
+<pre><code>~/$USER/crunch_scripts$ <span class="userinput">cd ~</span>
+~$ <span class="userinput">cat &gt;the_pipeline &lt;&lt;EOF
+{
+  "name":"My md5 pipeline",
+  "components":{
+    "do_hash":{
+      "script":"hash.py",
+      "script_parameters":{
+        "input":{
+          "required": true,
+          "dataclass": "Collection"
+        }
+      },
+      "repository":"$USER",
+      "script_version":"master",
+      "output_is_persistent":true,
+      "runtime_constraints":{
+        "docker_image":"arvados/jobs-java-bwa-samtools"
+      }
+    }
+  }
+}
+EOF
+</span></code></pre>
+</notextile>
+
+* @"repository"@ is the name of a git repository to search for the script version.  You can access a list of available git repositories on the Arvados Workbench under "Code repositories":https://{{site.arvados_workbench_host}}/repositories.
+* @"script_version"@ specifies the version of the script that you wish to run.  This can be in the form of an explicit Git revision hash, a tag, or a branch (in which case it will use the HEAD of the specified branch).  Arvados logs the script version that was used in the run, enabling you to go back and re-run any past job with the guarantee that the exact same code will be used as was used in the previous run.
+* @"script"@ specifies the filename of the script to run.  Crunch expects to find this in the @crunch_scripts/@ subdirectory of the Git repository.
+
+Now, use @arv pipeline_template create@ to register your pipeline template in Arvados:
+
+<notextile>
+<pre><code>~$ <span class="userinput">arv pipeline_template create --pipeline-template "$(cat the_pipeline)"</span>
+</code></pre>
+</notextile>
+
+h2. Running your pipeline
+
+Your new pipeline template should appear at the top of the Workbench "pipeline&nbsp;templates":https://{{ site.arvados_workbench_host }}/pipeline_templates page.  You can run your pipeline "using Workbench":tutorial-pipeline-workbench.html or the "command line.":{{site.baseurl}}/user/topics/running-pipeline-command-line.html
+
+For more information and examples for writing pipelines, see the "pipeline template reference":{{site.baseurl}}/api/schema/PipelineTemplate.html
index 99a0b4c527a9363abd645f33b4e0448f51ecff82..6a70fc30eb3672e5069d4c4a7771cfda5fcab17e 100644 (file)
@@ -4,14 +4,17 @@ FROM arvados/passenger
 MAINTAINER Tim Pierce <twp@curoverse.com>
 
 # Install postgres and apache.
-# Clone a git repository of Arvados source -- not used to build, but
-# will be used by the Commit model and anything else that needs to
-# check a git repo for crunch scripts.
-#
 RUN apt-get update && \
-    apt-get -q -y install procps postgresql postgresql-server-dev-9.1 apache2 \
-                          supervisor && \
-    git clone --bare git://github.com/curoverse/arvados.git /var/cache/git/arvados.git
+    apt-get -q -y install procps postgresql postgresql-server-dev-9.1 apache2 slurm-llnl munge \
+                          supervisor sudo libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
+                          libjson-perl cron
+
+ADD munge.key /etc/munge/
+RUN chown munge:munge /etc/munge/munge.key && chmod 600 /etc/munge/munge.key
+ADD generated/slurm.conf /etc/slurm-llnl/
+
+RUN /usr/local/rvm/bin/rvm-exec default gem install arvados-cli arvados
+# /for crunch-dispatch
 
 RUN /bin/mkdir -p /usr/src/arvados/services
 ADD generated/api.tar.gz /usr/src/arvados/services/
@@ -47,9 +50,29 @@ RUN a2dissite default && \
     a2enmod ssl && \
     /bin/mkdir /var/run/apache2
 
+# Install a token for root
+RUN mkdir -p /root/.config/arvados; echo "ARVADOS_API_HOST=api" >> /root/.config/arvados/settings.conf && echo "ARVADOS_API_HOST_INSECURE=yes" >> /root/.config/arvados/settings.conf && echo "ARVADOS_API_TOKEN=$(cat /tmp/superuser_token)" >> /root/.config/arvados/settings.conf && chmod 600 /root/.config/arvados/settings.conf
+
+# Set up directory for job commit repo
+RUN mkdir -p /var/lib/arvados
+# Add crunch user
+RUN addgroup --gid 4005 crunch && mkdir /home/crunch && useradd --uid 4005 --gid 4005 crunch && chown crunch:crunch /home/crunch
+
+# Create keep and compute node objects
+ADD keep_server_0.json /root/
+ADD keep_server_1.json /root/
+
+# Set up update-gitolite.rb
+RUN mkdir /usr/local/arvados/config -p
+ADD generated/arvados-clients.yml /usr/local/arvados/config/
+ADD update-gitolite.rb /usr/local/arvados/
+
 # Supervisor.
 ADD supervisor.conf /etc/supervisor/conf.d/arvados.conf
 ADD ssh.sh /usr/local/bin/ssh.sh
+ADD generated/setup.sh /usr/local/bin/setup.sh
+ADD generated/setup-gitolite.sh /usr/local/bin/setup-gitolite.sh
+ADD crunch-dispatch-run.sh /usr/local/bin/crunch-dispatch-run.sh
 ADD apache2_foreground.sh /etc/apache2/foreground.sh
 
 # Start the supervisor.
index 056d4b9263d19c6b2683ee45c80268020179af6b..355c4e5f94be8aa4ceda83272daffd00433e311b 100644 (file)
@@ -50,6 +50,11 @@ production:
   #     Net::HTTP.get(URI("http://169.254.169.254/latest/meta-data/#{iface}-ipv4")).match(/^[\d\.]+$/)[0]
   #   end << '172.16.0.23'
   # %>
+  permit_create_collection_with_unsigned_manifest: true
+  git_repositories_dir: /home/git/repositories
+  crunch_job_wrapper: :slurm_immediate
+  action_mailer.raise_delivery_errors: false
+  action_mailer.perform_deliveries: false
 
 test:
   uuid_prefix: zzzzz
diff --git a/docker/api/apt.arvados.org.list b/docker/api/apt.arvados.org.list
new file mode 100644 (file)
index 0000000..7eb8716
--- /dev/null
@@ -0,0 +1,2 @@
+# apt.arvados.org
+deb http://apt.arvados.org/ wheezy main
diff --git a/docker/api/arvados-clients.yml.in b/docker/api/arvados-clients.yml.in
new file mode 100644 (file)
index 0000000..babfc4e
--- /dev/null
@@ -0,0 +1,5 @@
+production:
+  gitolite_url: 'git@api:gitolite-admin.git'
+  gitolite_tmp: 'gitolite-tmp'
+  arvados_api_host: 'api'
+  arvados_api_token: '@@API_SUPERUSER_SECRET@@'
diff --git a/docker/api/crunch-dispatch-run.sh b/docker/api/crunch-dispatch-run.sh
new file mode 100755 (executable)
index 0000000..5103b1d
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+set -e
+export PATH="$PATH":/usr/src/arvados/services/crunch
+export PERLLIB=/usr/src/arvados/sdk/perl/lib
+export ARVADOS_API_HOST=api
+export ARVADOS_API_HOST_INSECURE=yes
+export CRUNCH_DISPATCH_LOCKFILE=/var/lock/crunch-dispatch
+
+if [[ ! -e $CRUNCH_DISPATCH_LOCKFILE ]]; then
+  touch $CRUNCH_DISPATCH_LOCKFILE
+fi
+
+export CRUNCH_JOB_BIN=/usr/src/arvados/services/crunch/crunch-job
+export HOME=`pwd`
+fuser -TERM -k $CRUNCH_DISPATCH_LOCKFILE || true
+
+# Give the compute nodes some time to start up
+sleep 5
+
+cd /usr/src/arvados/services/api
+export RAILS_ENV=production
+/usr/local/rvm/bin/rvm-exec default bundle install
+exec /usr/local/rvm/bin/rvm-exec default bundle exec ./script/crunch-dispatch.rb 2>&1
+
diff --git a/docker/api/keep_server_0.json b/docker/api/keep_server_0.json
new file mode 100644 (file)
index 0000000..ce02f50
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "service_host": "keep_server_0.keep.dev.arvados",
+  "service_port": 25107,
+  "service_ssl_flag": "false",
+  "service_type": "disk"
+}
diff --git a/docker/api/keep_server_1.json b/docker/api/keep_server_1.json
new file mode 100644 (file)
index 0000000..dbbdd1c
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "service_host": "keep_server_1.keep.dev.arvados",
+  "service_port": 25107,
+  "service_ssl_flag": "false",
+  "service_type": "disk"
+}
+
diff --git a/docker/api/munge.key b/docker/api/munge.key
new file mode 100644 (file)
index 0000000..34036a0
Binary files /dev/null and b/docker/api/munge.key differ
index 613f72d1b966a366940d162d6e910a6ecc59e2d2..198668e0fc39d2a21f8b6ae83f2508ac59da8b07 100644 (file)
@@ -4,7 +4,11 @@ APP_ID = '@@SSO_CLIENT_APP_ID@@'
 APP_SECRET = '@@SSO_CLIENT_SECRET@@'
 
 # Update your custom Omniauth provider URL here
-CUSTOM_PROVIDER_URL = 'https://' + ENV['SSO_PORT_443_TCP_ADDR'].to_s
+if '@@OMNIAUTH_URL@@' != ''
+  CUSTOM_PROVIDER_URL = '@@OMNIAUTH_URL@@'
+else
+  CUSTOM_PROVIDER_URL = 'https://' + ENV['SSO_PORT_443_TCP_ADDR'].to_s
+end
 
 # This is a development sandbox, we use self-signed certificates
 OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
diff --git a/docker/api/setup-gitolite.sh.in b/docker/api/setup-gitolite.sh.in
new file mode 100755 (executable)
index 0000000..92014f9
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/bash
+
+ssh-keygen -q -N '' -t rsa -f /root/.ssh/id_rsa
+
+useradd git
+mkdir /home/git
+
+# Set up gitolite repository
+cp ~root/.ssh/id_rsa.pub ~git/root-authorized_keys.pub
+chown git:git /home/git -R
+su - git -c "mkdir -p ~/bin"
+
+su - git -c "git clone git://github.com/sitaramc/gitolite"
+su - git -c "gitolite/install -ln ~/bin"
+su - git -c "PATH=/home/git/bin:$PATH gitolite setup -pk ~git/root-authorized_keys.pub"
+
+# Make sure the repositories are created in such a way that they are readable
+# by the api server
+sed -i 's/0077/0022/g' /home/git/.gitolite.rc
+
+# And make sure that the existing repos are equally readable, or the API server commit model will freak out...
+chmod 755 /home/git/repositories
+chmod +rx /home/git/repositories/*git -R
+
+# Now set up the gitolite repo(s) we use
+mkdir -p /usr/local/arvados/gitolite-tmp/
+# Make ssh store the host key
+ssh -o "StrictHostKeyChecking no" git@api info
+# Now check out the tree
+git clone git@api:gitolite-admin.git /usr/local/arvados/gitolite-tmp/gitolite-admin/
+cd /usr/local/arvados/gitolite-tmp/gitolite-admin
+mkdir keydir/arvados
+mkdir conf/admin
+mkdir conf/auto
+echo "
+
+@arvados_git_user = arvados_git_user
+
+repo @all
+     RW+                 = @arvados_git_user
+
+" > conf/admin/arvados.conf
+echo '
+include "auto/*.conf"
+include "admin/*.conf"
+' >> conf/gitolite.conf
+
+#su - git -c "ssh-keygen -t rsa"
+cp /root/.ssh/id_rsa.pub keydir/arvados/arvados_git_user.pub
+# Replace the 'root' key with the user key, just in case
+cp /root/.ssh/authorized_keys keydir/root-authorized_keys.pub
+# But also make sure we have the root key installed so it can access all keys
+git add keydir/root-authorized_keys.pub
+git add keydir/arvados/arvados_git_user.pub
+git add conf/admin/arvados.conf
+git add keydir/arvados/
+git add conf/gitolite.conf
+git commit -a -m 'git server setup'
+git push
+
+# Prepopulate the arvados.git repo with our source. Silly, but until we can check out from remote trees,
+# we need this to make the tutorials work.
+su - git -c "git clone --bare git://github.com/curoverse/arvados.git /home/git/repositories/arvados.git"
+
+echo "ARVADOS_API_HOST_INSECURE=yes" > /etc/cron.d/gitolite-update
+echo "*/2 * * * * root /bin/bash -c 'source /etc/profile.d/rvm.sh && /usr/local/arvados/update-gitolite.rb production'" >> /etc/cron.d/gitolite-update
+
+# Create/update the repos now
+. /etc/profile.d/rvm.sh
+export ARVADOS_API_HOST=api
+export ARVADOS_API_HOST_INSECURE=yes
+export ARVADOS_API_TOKEN=@@API_SUPERUSER_SECRET@@
+/usr/local/arvados/update-gitolite.rb production
+
+echo "PATH=/usr/bin:/bin:/sbin" > /etc/cron.d/arvados-repo-update
+echo "*/5 * * * * git cd ~git/repositories/arvados.git; git fetch https://github.com/curoverse/arvados.git master:master" >> /etc/cron.d/arvados-repo-update
+
diff --git a/docker/api/setup.sh.in b/docker/api/setup.sh.in
new file mode 100755 (executable)
index 0000000..cba4759
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+set -x
+
+. /etc/profile.d/rvm.sh
+
+export ARVADOS_API_HOST=api
+export ARVADOS_API_HOST_INSECURE=yes
+export ARVADOS_API_TOKEN=@@API_SUPERUSER_SECRET@@
+
+# All users group
+prefix=`arv --format=uuid user current | cut -d- -f1`
+read -rd $'\000' newgroup <<EOF; arv group create --group "$newgroup"
+{
+ "uuid":"$prefix-j7d0g-fffffffffffffff",
+ "name":"All users"
+}
+EOF
+
+# Arvados repository object
+all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
+repo_uuid=`arv --format=uuid repository create --repository '{"name":"arvados","fetch_url":"git@api:arvados.git","push_url":"git@api:arvados.git"}'`
+echo "Arvados repository uuid is $repo_uuid"
+
+read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
+{
+ "tail_uuid":"$all_users_group_uuid",
+ "head_uuid":"$repo_uuid",
+ "link_class":"permission",
+ "name":"can_read"
+}
+EOF
+
+# Make sure the necessary keep_service objects exist
+arv keep_service list > /tmp/keep_service.list
+
+grep -q keep_server_0 /tmp/keep_service.list
+if [[ "$?" != "0" ]]; then
+  arv keep_service create --keep-service "$(cat /root/keep_server_0.json)"
+fi
+
+grep -q keep_server_1 /tmp/keep_service.list
+if [[ "$?" != "0" ]]; then
+  arv keep_service create --keep-service "$(cat /root/keep_server_1.json)"
+fi
+
+# User repository object
+user_uuid=`arv --format=uuid user current`
+repo_uuid=`arv --format=uuid repository create --repository '{"name":"@@ARVADOS_USER_NAME@@","fetch_url":"git@api:@@ARVADOS_USER_NAME@@.git","push_url":"git@api:@@ARVADOS_USER_NAME@@.git"}'`
+echo "User repository uuid is $repo_uuid"
+
+read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
+{
+ "tail_uuid":"$user_uuid",
+ "head_uuid":"$repo_uuid",
+ "link_class":"permission",
+ "name":"can_write"
+}
+EOF
+
+# Shell machine object
+arv virtual_machine create --virtual-machine '{"hostname":"shell"}'
diff --git a/docker/api/slurm.conf.in b/docker/api/slurm.conf.in
new file mode 100644 (file)
index 0000000..7312a0e
--- /dev/null
@@ -0,0 +1,60 @@
+
+ControlMachine=api
+#SlurmUser=slurmd
+SlurmctldPort=6817
+SlurmdPort=6818
+AuthType=auth/munge
+#JobCredentialPrivateKey=/etc/slurm-llnl/slurm-key.pem
+#JobCredentialPublicCertificate=/etc/slurm-llnl/slurm-cert.pem
+StateSaveLocation=/tmp
+SlurmdSpoolDir=/tmp/slurmd
+SwitchType=switch/none
+MpiDefault=none
+SlurmctldPidFile=/var/run/slurmctld.pid
+SlurmdPidFile=/var/run/slurmd.pid
+ProctrackType=proctrack/pgid
+CacheGroups=0
+ReturnToService=2
+TaskPlugin=task/affinity
+#
+# TIMERS
+SlurmctldTimeout=300
+SlurmdTimeout=300
+InactiveLimit=0
+MinJobAge=300
+KillWait=30
+Waittime=0
+#
+# SCHEDULING
+SchedulerType=sched/backfill
+#SchedulerType=sched/builtin
+SchedulerPort=7321
+#SchedulerRootFilter=
+#SelectType=select/linear
+SelectType=select/cons_res
+SelectTypeParameters=CR_CPU_Memory
+FastSchedule=1
+#
+# LOGGING
+SlurmctldDebug=3
+#SlurmctldLogFile=
+SlurmdDebug=3
+#SlurmdLogFile=
+JobCompType=jobcomp/none
+#JobCompLoc=
+JobAcctGatherType=jobacct_gather/none
+#JobAcctLogfile=
+#JobAcctFrequency=
+#
+# COMPUTE NODES
+NodeName=DEFAULT
+# CPUs=8 State=UNKNOWN RealMemory=6967 Weight=6967
+PartitionName=DEFAULT MaxTime=INFINITE State=UP
+PartitionName=compute Default=YES Shared=yes
+#PartitionName=sysadmin Hidden=YES Shared=yes
+
+NodeName=compute[0-1]
+#NodeName=compute0 RealMemory=6967 Weight=6967
+
+PartitionName=compute Nodes=compute[0-1]
+PartitionName=crypto Nodes=compute[0-1]
index a4f91296156590de77cb72a09f4b3c4de043bbd9..e85bb72658ee48dbb464a5aa088e0403c7ca1054 100644 (file)
@@ -10,3 +10,32 @@ command=/usr/lib/postgresql/9.1/bin/postgres -D /var/lib/postgresql/9.1/main -c
 [program:apache2]
 command=/etc/apache2/foreground.sh
 stopsignal=6
+
+[program:munge]
+user=root
+command=/etc/init.d/munge start
+startsecs=0
+
+[program:slurm]
+user=root
+command=/etc/init.d/slurm-llnl start
+startsecs=0
+
+[program:cron]
+user=root
+command=/etc/init.d/cron start
+startsecs=0
+
+[program:setup]
+user=root
+command=/usr/local/bin/setup.sh
+startsecs=0
+
+[program:setup-gitolite]
+user=root
+command=/usr/local/bin/setup-gitolite.sh
+startsecs=0
+
+[program:crunch-dispatch]
+user=root
+command=/usr/local/bin/crunch-dispatch-run.sh
diff --git a/docker/api/update-gitolite.rb b/docker/api/update-gitolite.rb
new file mode 100755 (executable)
index 0000000..779099a
--- /dev/null
@@ -0,0 +1,162 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'pp'
+require 'arvados'
+require 'active_support/all'
+
+# This script does the actual gitolite config management on disk.
+#
+# Ward Vandewege <ward@curoverse.com>
+
+# Default is development
+production = ARGV[0] == "production"
+
+ENV["RAILS_ENV"] = "development"
+ENV["RAILS_ENV"] = "production" if production
+
+DEBUG = 1
+
+# load and merge in the environment-specific application config info
+# if present, overriding base config parameters as specified
+path = File.dirname(__FILE__) + '/config/arvados-clients.yml'
+if File.exists?(path) then
+  cp_config = YAML.load_file(path)[ENV['RAILS_ENV']]
+else
+  puts "Please create a\n " + File.dirname(__FILE__) + "/config/arvados-clients.yml\n file"
+  exit 1
+end
+
+gitolite_url = cp_config['gitolite_url']
+gitolite_tmp = cp_config['gitolite_tmp']
+
+gitolite_admin = File.join(File.expand_path(File.dirname(__FILE__)) + '/' + gitolite_tmp + '/gitolite-admin')
+
+ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
+ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
+
+keys = ''
+
+seen = Hash.new
+
+def ensure_repo(name,permissions,user_keys,gitolite_admin)
+  tmp = ''
+  # Just in case...
+  name.gsub!(/[^a-z0-9]/i,'')
+
+  keys = Hash.new()
+
+  user_keys.each do |uuid,p|
+    p.each do |k|
+      next if k[:public_key].nil?
+      keys[uuid] = Array.new() if not keys.key?(uuid)
+
+      key = k[:public_key]
+      # Handle putty-style ssh public keys
+      key.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1')
+      key.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1')
+      key.gsub!(/\n/,'')
+      key.strip
+
+      keys[uuid].push(key)
+    end
+  end
+
+  cf = gitolite_admin + '/conf/auto/' + name + '.conf'
+
+  conf = "\nrepo #{name}\n"
+
+  commit = false
+
+  seen = {}
+  permissions.sort.each do |uuid,v|
+    conf += "\t#{v[:gitolite_permissions]}\t= #{uuid.to_s}\n"
+
+    count = 0
+    keys.include?(uuid) and keys[uuid].each do |v|
+      kf = gitolite_admin + '/keydir/arvados/' + uuid.to_s + "@#{count}.pub"
+      seen[kf] = true
+      if !File.exists?(kf) or IO::read(kf) != v then
+        commit = true
+        f = File.new(kf + ".tmp",'w')
+        f.write(v)
+        f.close()
+        # File.rename will overwrite the destination file if it exists
+        File.rename(kf + ".tmp",kf);
+      end
+      count += 1
+    end
+  end
+
+  if !File.exists?(cf) or IO::read(cf) != conf then
+    commit = true
+    f = File.new(cf + ".tmp",'w')
+    f.write(conf)
+    f.close()
+    # this is about as atomic as we can make the replacement of the file...
+    File.unlink(cf) if File.exists?(cf)
+    File.rename(cf + ".tmp",cf);
+  end
+
+  return commit,seen
+end
+
+begin
+
+  pwd = Dir.pwd
+  # Get our local gitolite-admin repo up to snuff
+  if not File.exists?(File.dirname(__FILE__) + '/' + gitolite_tmp) then
+    Dir.mkdir(File.join(File.dirname(__FILE__) + '/' + gitolite_tmp), 0700)
+  end
+  if not File.exists?(gitolite_admin) then
+    Dir.chdir(File.join(File.dirname(__FILE__) + '/' + gitolite_tmp))
+    `git clone #{gitolite_url}`
+  else
+    Dir.chdir(gitolite_admin)
+    `git pull`
+  end
+  Dir.chdir(pwd)
+
+  arv = Arvados.new( { :suppress_ssl_warnings => false } )
+
+  permissions = arv.repository.get_all_permissions
+
+  repos = permissions[:repositories]
+  user_keys = permissions[:user_keys]
+
+  @commit = false
+
+  @seen = {}
+
+  repos.each do |r|
+    next if r[:name].nil?
+    (@c,@s) = ensure_repo(r[:name],r[:user_permissions],user_keys,gitolite_admin)
+    @seen.merge!(@s)
+    @commit = true if @c
+  end
+
+  # Clean up public key files that should not be present
+  Dir.glob(gitolite_admin + '/keydir/arvados/*.pub') do |key_file|
+    next if key_file =~ /arvados_git_user.pub$/
+    next if @seen.has_key?(key_file)
+    puts "Extra file #{key_file}"
+    @commit = true
+    Dir.chdir(gitolite_admin)
+    key_file.gsub!(/^#{gitolite_admin}\//,'')
+    `git rm #{key_file}`
+  end
+
+  if @commit then
+    message = "#{Time.now().to_s}: update from API"
+    Dir.chdir(gitolite_admin)
+    `git add --all`
+    `git commit -m '#{message}'`
+    `git push`
+  end
+
+rescue Exception => bang
+  puts "Error: " + bang.to_s
+  puts bang.backtrace.join("\n")
+  exit 1
+end
+
index f2edc19c737107857e6216b41de52f2e04308d09..e36e5cfd597cb06c7f0a89646eb6838177a0655a 100755 (executable)
@@ -7,18 +7,23 @@ if [[ "$DOCKER" == "" ]]; then
     DOCKER=`which docker`
 fi
 
+COMPUTE_COUNTER=0
+
 function usage {
     echo >&2
     echo >&2 "usage: $0 (start|stop|restart|test) [options]"
     echo >&2
     echo >&2 "$0 start/stop/restart options:"
-    echo >&2 "  -d [port], --doc[=port]        Documentation server (default port 9898)"
-    echo >&2 "  -w [port], --workbench[=port]  Workbench server (default port 9899)"
-    echo >&2 "  -s [port], --sso[=port]        SSO server (default port 9901)"
-    echo >&2 "  -a [port], --api[=port]        API server (default port 9900)"
-    echo >&2 "  -k, --keep                     Keep servers"
-    echo >&2 "  --ssh                          Enable SSH access to server containers"
-    echo >&2 "  -h, --help                     Display this help and exit"
+    echo >&2 "  -d[port], --doc[=port]        Documentation server (default port 9898)"
+    echo >&2 "  -w[port], --workbench[=port]  Workbench server (default port 9899)"
+    echo >&2 "  -s[port], --sso[=port]        SSO server (default port 9901)"
+    echo >&2 "  -a[port], --api[=port]        API server (default port 9900)"
+    echo >&2 "  -c, --compute                 Compute nodes (starts 2)"
+    echo >&2 "  -v, --vm                      Shell server"
+    echo >&2 "  -n, --nameserver              Nameserver"
+    echo >&2 "  -k, --keep                    Keep servers"
+    echo >&2 "  --ssh                         Enable SSH access to server containers"
+    echo >&2 "  -h, --help                    Display this help and exit"
     echo >&2
     echo >&2 "  If no options are given, the action is applied to all servers."
     echo >&2
@@ -39,7 +44,16 @@ function start_container {
     fi
     if [[ "$2" != '' ]]; then
       local name="$2"
-      args="$args --name $name"
+      if [[ "$name" == "api_server" ]]; then
+        args="$args --dns=172.17.42.1 --dns-search=compute.dev.arvados --hostname api -P --name $name"
+      elif [[ "$name" == "compute" ]]; then
+        name=$name$COMPUTE_COUNTER
+        # We need --privileged because we run docker-inside-docker on the compute nodes
+        args="$args --dns=172.17.42.1 --dns-search=compute.dev.arvados --hostname compute$COMPUTE_COUNTER -P --privileged --name $name"
+        let COMPUTE_COUNTER=$(($COMPUTE_COUNTER + 1))
+      else
+        args="$args --dns=172.17.42.1 --dns-search=dev.arvados --hostname ${name#_server} --name $name"
+      fi
     fi
     if [[ "$3" != '' ]]; then
       local volume="$3"
@@ -66,13 +80,14 @@ function start_container {
     $DOCKER rm "$name" 2>/dev/null
 
     echo "Starting container:"
+    #echo "  $DOCKER run --dns=127.0.0.1 $args $image"
     echo "  $DOCKER run $args $image"
     container=`$DOCKER run $args $image`
-    if [[ "$?" != "0" ]]; then
+    if [ "$?" != "0" -o "$container" = "" ]; then
       echo "Unable to start container"
       exit 1
     fi
-    if $ENABLE_SSH
+    if [ "$name" == "compute" -o "$ENABLE_SSH" != "false" ];
     then
       ip=$(ip_address $container )
       echo
@@ -80,6 +95,8 @@ function start_container {
       echo
       echo "    ssh root@$ip"
       echo
+    else
+      echo "Started container: $container"
     fi
 
     if [[ "$name" == "doc_server" ]]; then
@@ -130,12 +147,15 @@ function do_start {
     local start_doc=false
     local start_sso=false
     local start_api=false
+    local start_compute=false
     local start_workbench=false
+    local start_vm=false
+    local start_nameserver=false
     local start_keep=false
 
     # NOTE: This requires GNU getopt (part of the util-linux package on Debian-based distros).
-    local TEMP=`getopt -o d::s::a::w::kh \
-                  --long doc::,sso::,api::,workbench::,keep,help,ssh \
+    local TEMP=`getopt -o d::s::a::cw::nkvh \
+                  --long doc::,sso::,api::,compute,workbench::,nameserver,keep,vm,help,ssh \
                   -n "$0" -- "$@"`
 
     if [ $? != 0 ] ; then echo "Use -h for help"; exit 1 ; fi
@@ -164,12 +184,24 @@ function do_start {
                     *)  start_api=$2; shift 2 ;;
                 esac
                 ;;
+            -c | --compute)
+                start_compute=2
+                shift
+                ;;
             -w | --workbench)
                 case "$2" in
                     "") start_workbench=9899; shift 2 ;;
                     *)  start_workbench=$2; shift 2 ;;
                 esac
                 ;;
+            -v | --vm)
+                start_vm=true
+                shift
+                ;;
+            -n | --nameserver)
+                start_nameserver=true
+                shift
+                ;;
             -k | --keep)
                 start_keep=true
                 shift
@@ -194,13 +226,19 @@ function do_start {
     if [[ $start_doc == false &&
           $start_sso == false &&
           $start_api == false &&
+          $start_compute == false &&
           $start_workbench == false &&
+          $start_vm == false &&
+          $start_nameserver == false &&
           $start_keep == false ]]
     then
         start_doc=9898
         start_sso=9901
         start_api=9900
+        start_compute=2
         start_workbench=9899
+        start_vm=true
+        start_nameserver=true
         start_keep=true
     fi
 
@@ -214,6 +252,31 @@ function do_start {
         start_container "$start_api:443" "api_server" '' "sso_server:sso" "arvados/api"
     fi
 
+    if [[ $start_nameserver != false ]]
+    then
+      # We rely on skydock and skydns for dns discovery between the slurm controller and compute nodes,
+      # so make sure they are running
+      $DOCKER ps | grep skydns >/dev/null
+      if [[ "$?" != "0" ]]; then
+        echo "Starting crosbymichael/skydns container..."
+        $DOCKER rm "skydns" 2>/dev/null
+        $DOCKER run -d -p 172.17.42.1:53:53/udp --name skydns crosbymichael/skydns -nameserver 8.8.8.8:53 -domain arvados
+      fi
+      $DOCKER ps | grep skydock >/dev/null
+      if [[ "$?" != "0" ]]; then
+        echo "Starting crosbymichael/skydock container..."
+        $DOCKER rm "skydock" 2>/dev/null
+        $DOCKER run -d -v /var/run/docker.sock:/docker.sock --name skydock crosbymichael/skydock -ttl 30 -environment dev -s /docker.sock -domain arvados -name skydns
+      fi
+    fi
+
+    if [[ $start_compute != false ]]
+    then
+        for i in `seq 0 $(($start_compute - 1))`; do
+          start_container "" "compute" '' "api_server:api" "arvados/compute"
+        done
+    fi
+
     if [[ $start_keep != false ]]
     then
         # create `keep_volumes' array with a list of keep mount points
@@ -224,11 +287,11 @@ function do_start {
             [ -f $v/keep/.metadata.yml ] && sudo rm $v/keep/.metadata.yml
         done
         start_container "25107:25107" "keep_server_0" \
-            "${keep_volumes[0]}:/dev/keep-0" \
+            "${keep_volumes[0]}:/keep-data" \
             "api_server:api" \
             "arvados/keep"
         start_container "25108:25107" "keep_server_1" \
-            "${keep_volumes[1]}:/dev/keep-0" \
+            "${keep_volumes[1]}:/keep-data" \
             "api_server:api" \
             "arvados/keep"
     fi
@@ -238,6 +301,11 @@ function do_start {
         start_container "$start_doc:80" "doc_server" '' '' "arvados/doc"
     fi
 
+    if [[ $start_vm != false ]]
+    then
+        start_container "" "shell" '' "api_server:api" "arvados/shell"
+    fi
+
     if [[ $start_workbench != false ]]
     then
         start_container "$start_workbench:80" "workbench_server" '' "api_server:api" "arvados/workbench"
@@ -258,12 +326,15 @@ function do_stop {
     local stop_doc=""
     local stop_sso=""
     local stop_api=""
+    local stop_compute=""
     local stop_workbench=""
+    local stop_nameserver=""
+    local stop_vm=""
     local stop_keep=""
 
     # NOTE: This requires GNU getopt (part of the util-linux package on Debian-based distros).
-    local TEMP=`getopt -o d::s::a::w::kh \
-                  --long doc::,sso::,api::,workbench::,keep,help,ssh \
+    local TEMP=`getopt -o dsacwnkvh \
+                  --long doc,sso,api,compute,workbench,nameserver,keep,vm,help \
                   -n "$0" -- "$@"`
 
     if [ $? != 0 ] ; then echo "Use -h for help"; exit 1 ; fi
@@ -275,18 +346,21 @@ function do_stop {
     do
         case $1 in
             -d | --doc)
-                stop_doc=doc_server ; shift ;;
+                stop_doc=doc_server ; shift ;;
             -s | --sso)
-                stop_sso=sso_server ; shift ;;
+                stop_sso=sso_server ; shift ;;
             -a | --api)
-                stop_api=api_server ; shift 2 ;;
+                stop_api=api_server ; shift ;;
+            -c | --compute)
+                stop_compute=`$DOCKER ps |grep -P "compute\d+" |grep -v api_server |cut -f1 -d ' '` ; shift ;;
             -w | --workbench)
-                stop_workbench=workbench_server ; shift 2 ;;
+                stop_workbench=workbench_server ; shift ;;
+            -n | --nameserver )
+                stop_nameserver="skydock skydns" ; shift ;;
+            -v | --vm )
+                stop_vm="shell" ; shift ;;
             -k | --keep )
                 stop_keep="keep_server_0 keep_server_1" ; shift ;;
-            --ssh)
-                shift
-                ;;
             --)
                 shift
                 break
@@ -302,17 +376,23 @@ function do_stop {
     if [[ $stop_doc == "" &&
           $stop_sso == "" &&
           $stop_api == "" &&
+          $stop_compute == "" &&
           $stop_workbench == "" &&
+          $stop_vm == "" &&
+          $stop_nameserver == "" &&
           $stop_keep == "" ]]
     then
         stop_doc=doc_server
         stop_sso=sso_server
         stop_api=api_server
+        stop_compute=`$DOCKER ps |grep -P "compute\d+" |grep -v api_server |cut -f1 -d ' '`
         stop_workbench=workbench_server
+        stop_vm=shell
+        stop_nameserver="skydock skydns"
         stop_keep="keep_server_0 keep_server_1"
     fi
 
-    $DOCKER stop $stop_doc $stop_sso $stop_api $stop_workbench $stop_keep \
+    $DOCKER stop $stop_doc $stop_sso $stop_api $stop_compute $stop_workbench $stop_nameserver $stop_keep $stop_vm \
         2>/dev/null
 }
 
index f1defceb7d2f2a1e85eaadb3ad7d81e25ec70263..79cb42444ec33ab1a7a1ec822f8e9edd56534af7 100644 (file)
@@ -22,6 +22,10 @@ RUN apt-get update && \
     /usr/local/rvm/bin/rvm alias create default ruby && \
     /bin/mkdir -p /usr/src/arvados
 
+ADD apt.arvados.org.list /etc/apt/sources.list.d/
+RUN apt-key adv --keyserver pgp.mit.edu --recv 1078ECD7
+RUN apt-get update && apt-get -qqy install python-arvados-python-client
+
 ADD generated/arvados.tar.gz /usr/src/arvados/
 
 # Update gem. This (hopefully) fixes
diff --git a/docker/base/apt.arvados.org.list b/docker/base/apt.arvados.org.list
new file mode 100644 (file)
index 0000000..7eb8716
--- /dev/null
@@ -0,0 +1,2 @@
+# apt.arvados.org
+deb http://apt.arvados.org/ wheezy main
index cbcc840667c4001365d5644be6fc4e1b16817d90..77aeb1f1878a7d4404e432c08c5ad880b4d0bfed 100755 (executable)
@@ -1,14 +1,41 @@
 #! /bin/bash
 
-# make sure Ruby 1.9.3 is installed before proceeding
+# make sure a Ruby version greater than or equal to 1.9.3 is installed before proceeding
 if ! ruby -e 'exit RUBY_VERSION >= "1.9.3"' 2>/dev/null
 then
-    echo "Installing Arvados requires at least Ruby 1.9.3."
-    echo "You may need to enter your password."
-    read -p "Press Ctrl-C to abort, or else press ENTER to install ruby1.9.3 and continue. " unused
+    echo "Building the Arvados docker containers requires at least Ruby 1.9.3."
+    echo "Please install ruby 1.9.3 or higher before executing this script."
+    exit 1
+fi
+
+function usage {
+    echo >&2
+    echo >&2 "usage: $0 [options]"
+    echo >&2
+    echo >&2 "Calling $0 without arguments will build all Arvados docker images"
+    echo >&2
+    echo >&2 "$0 options:"
+    echo >&2 "  -h, --help   Print this help text"
+    echo >&2 "  clean        Clear all build information"
+    echo >&2 "  realclean    clean and remove all Arvados Docker images except arvados/debian"
+    echo >&2 "  deepclean    realclean and remove arvados/debian, crosbymichael/skydns and "
+    echo >&2 "               crosbymichael/skydns Docker images"
+    echo >&2
+}
 
-    sudo apt-get update
-    sudo apt-get -y install ruby1.9.3
+if [ "$1" = '-h' ] || [ "$1" = '--help' ]; then
+  usage
+  exit 1
 fi
 
-build_tools/build.rb $*
+build_tools/build.rb
+
+if [[ "$?" == "0" ]]; then
+    DOCKER=`which docker.io`
+
+    if [[ "$DOCKER" == "" ]]; then
+      DOCKER=`which docker`
+    fi
+
+    DOCKER=$DOCKER /usr/bin/make -f build_tools/Makefile $*
+fi
index 267e24403650202580ef0e64816cf7c892e9e548..7f3ad6913908f088b4eee289c9e6e6292de25667 100644 (file)
@@ -1,18 +1,51 @@
-all: api-image doc-image workbench-image keep-image sso-image
+# This is the 'shell hack'. Call make with DUMP=1 to see the effect.
+ifdef DUMP
+OLD_SHELL := $(SHELL)
+SHELL = $(warning [$@])$(OLD_SHELL) -x
+endif
+
+all: skydns-image skydock-image api-image compute-image doc-image workbench-image keep-image sso-image shell-image
+
+IMAGE_FILES := $(shell ls *-image 2>/dev/null |grep -v -E 'debian-arvados-image|skydns-image|skydock-image')
+GENERATED_FILES := $(shell ls */generated/* 2>/dev/null)
+GENERATED_DIRS := $(shell ls */generated 2>/dev/null)
 
 # `make clean' removes the files generated in the build directory
 # but does not remove any docker images generated in previous builds
 clean:
-       -rm -rf build
-       -rm *-image */generated/*
-       -@rmdir */generated
-
-# `make realclean' will also remove the docker images and force
-# subsequent makes to build the entire chain from the ground up
+       @echo "make clean"
+       -@rm -rf build
+       +@[ "$(IMAGE_FILES)$(GENERATED_FILES)" = "" ] || rm $(IMAGE_FILES) $(GENERATED_FILES) 2>/dev/null
+       +@[ "$(GENERATED_DIRS)" = "" ] || rmdir */generated 2>/dev/null
+
+DEBIAN_IMAGE := $(shell $(DOCKER) images -q arvados/debian |head -n1)
+
+REALCLEAN_CONTAINERS := $(shell $(DOCKER) ps -a |grep -e arvados -e api_server -e keep_server -e doc_server -e workbench_server |cut -f 1 -d' ')
+REALCLEAN_IMAGES := $(shell $(DOCKER) images -q arvados/* |grep -v $(DEBIAN_IMAGE) 2>/dev/null)
+DEEPCLEAN_IMAGES := $(shell $(DOCKER) images -q arvados/*)
+SKYDNS_CONTAINERS := $(shell $(DOCKER) ps -a |grep -e crosbymichael/skydns -e crosbymichael/skydock |cut -f 1 -d' ')
+SKYDNS_IMAGES := $(shell $(DOCKER) images -q crosbymichael/skyd*)
+
+# `make realclean' will also remove the Arvados docker images (but not the
+# arvados/debian image) and force subsequent makes to build the entire chain
+# from the ground up
 realclean: clean
-       -[ -n "`$(DOCKER) ps -q`" ] && $(DOCKER) stop `$(DOCKER) ps -q`
-       -$(DOCKER) rm `$(DOCKER) ps -a |grep -e arvados -e api_server -e keep_server -e doc_server -e workbench_server |cut -f 1 -d' '`
-       -$(DOCKER) rmi `$(DOCKER) images -q arvados/*`
+       @echo "make realclean"
+       +@[ "`$(DOCKER) ps -q`" = '' ] || $(DOCKER) stop `$(DOCKER) ps -q`
+       +@[ "$(REALCLEAN_CONTAINERS)" = '' ] || $(DOCKER) rm $(REALCLEAN_CONTAINERS)
+       +@[ "$(REALCLEAN_IMAGES)" = '' ] || $(DOCKER) rmi $(REALCLEAN_IMAGES)
+
+# `make deepclean' will remove all Arvados docker images and the skydns/skydock
+# images and force subsequent makes to build the entire chain from the ground up
+deepclean: clean
+       @echo "make deepclean"
+       -@rm -f debian-arvados-image 2>/dev/null
+       -@rm -f skydns-image skydock-image 2>/dev/null
+       +@[ "`$(DOCKER) ps -q`" = '' ] || $(DOCKER) stop `$(DOCKER) ps -q`
+       +@[ "$(REALCLEAN_CONTAINERS)" = '' ] || $(DOCKER) rm $(REALCLEAN_CONTAINERS)
+       +@[ "$(DEEPCLEAN_IMAGES)" = '' ] || $(DOCKER) rmi $(DEEPCLEAN_IMAGES)
+       +@[ "$(SKYDNS_CONTAINERS)" = '' ] || $(DOCKER) rm $(SKYDNS_CONTAINERS)
+       +@[ "$(SKYDNS_IMAGES)" = '' ] || $(DOCKER) rmi $(SKYDNS_IMAGES)
 
 # ============================================================
 # Dependencies for */generated files which are prerequisites
@@ -22,60 +55,61 @@ CONFIG_RB = build_tools/config.rb
 
 BUILD = build/.buildstamp
 
-BASE_DEPS = base/Dockerfile $(BASE_GENERATED)
+BASE_DEPS = base/Dockerfile config.yml $(BASE_GENERATED)
+
+SLURM_DEPS = slurm/Dockerfile config.yml $(SLURM_GENERATED)
 
 JOBS_DEPS = jobs/Dockerfile
 
 JAVA_BWA_SAMTOOLS_DEPS = java-bwa-samtools/Dockerfile
 
-API_DEPS = api/Dockerfile $(API_GENERATED)
+API_DEPS = api/* config.yml $(API_GENERATED)
+
+SHELL_DEPS = shell/* config.yml $(SHELL_GENERATED)
+
+COMPUTE_DEPS = compute/* config.yml $(COMPUTE_GENERATED)
 
 DOC_DEPS = doc/Dockerfile doc/apache2_vhost
 
 WORKBENCH_DEPS = workbench/Dockerfile \
+                 config.yml \
                  $(WORKBENCH_GENERATED)
 
-KEEP_DEPS = keep/Dockerfile
+KEEP_DEPS = keep/Dockerfile config.yml $(KEEP_GENERATED)
 
-SSO_DEPS = $(SSO_GENERATED)
+SSO_DEPS = config.yml $(SSO_GENERATED)
 
 BCBIO_NEXTGEN_DEPS = bcbio-nextgen/Dockerfile
 
 BASE_GENERATED = base/generated/arvados.tar.gz
 
-API_GENERATED = \
-        api/generated/apache2_vhost \
-        api/generated/config_databases.sh \
-        api/generated/database.yml \
-        api/generated/omniauth.rb \
-        api/generated/application.yml \
-        api/generated/superuser_token
-
-API_GENERATED_IN = \
-        api/apache2_vhost.in \
-        api/config_databases.sh.in \
-        api/database.yml.in \
-        api/omniauth.rb.in \
-        api/application.yml.in \
-        api/superuser_token.in
-
-WORKBENCH_GENERATED = \
-        workbench/generated/apache2_vhost \
-        workbench/generated/application.yml
-
-WORKBENCH_GENERATED_IN = \
-        workbench/apache2_vhost.in \
-        workbench/application.yml.in
-
-SSO_GENERATED = \
-        sso/generated/apache2_vhost \
-        sso/generated/seeds.rb \
-        sso/generated/secret_token.rb
-
-SSO_GENERATED_IN = \
-        sso/apache2_vhost.in \
-        sso/seeds.rb.in \
-        sso/secret_token.rb.in
+COMPUTE_GENERATED_IN   = compute/*.in
+COMPUTE_GENERATED      = compute/generated/*
+
+KEEP_GENERATED_IN      = keep/*.in
+KEEP_GENERATED         = keep/generated/*
+
+API_GENERATED_IN       = api/*.in
+API_GENERATED          = api/generated/*
+
+SHELL_GENERATED_IN     = shell/*.in
+SHELL_GENERATED        = shell/generated/*
+
+SLURM_GENERATED_IN     = slurm/*.in
+SLURM_GENERATED        = slurm/generated/*
+
+WORKBENCH_GENERATED_IN = workbench/*.in
+WORKBENCH_GENERATED    = workbench/generated/*
+
+SSO_GENERATED_IN       = sso/*.in
+SSO_GENERATED          = sso/generated/*
+
+KEEP_DEPS += keep/generated/bin/keepproxy
+KEEP_DEPS += keep/generated/bin/keepstore
+keep/generated/bin/%: $(wildcard build/services/%/*.go)
+       mkdir -p keep/generated/src/git.curoverse.com
+       ln -sfn ../../../../.. keep/generated/src/git.curoverse.com/arvados.git
+       GOPATH=$(shell pwd)/keep/generated go get $(@:keep/generated/bin/%=git.curoverse.com/arvados.git/services/%)
 
 $(BUILD):
        mkdir -p build
@@ -88,22 +122,32 @@ $(BUILD):
        cd build/sdk/ruby && gem build arvados.gemspec
        touch build/.buildstamp
 
-$(BASE_GENERATED): config.yml $(BUILD)
-       $(CONFIG_RB)
+$(SLURM_GENERATED): $(BUILD)
+       $(CONFIG_RB) slurm
+       mkdir -p slurm/generated
+
+$(BASE_GENERATED): $(BUILD)
+       $(CONFIG_RB) base
        mkdir -p base/generated
        tar -czf base/generated/arvados.tar.gz -C build .
 
-$(API_GENERATED): config.yml $(API_GENERATED_IN)
-       $(CONFIG_RB)
+$(API_GENERATED): $(API_GENERATED_IN)
+       $(CONFIG_RB) api
 
-$(WORKBENCH_GENERATED): config.yml $(WORKBENCH_GENERATED_IN)
-       $(CONFIG_RB)
+$(SHELL_GENERATED): $(SHELL_GENERATED_IN)
+       $(CONFIG_RB) shell
 
-$(WAREHOUSE_GENERATED): config.yml $(WAREHOUSE_GENERATED_IN)
-       $(CONFIG_RB)
+$(WORKBENCH_GENERATED): $(WORKBENCH_GENERATED_IN)
+       $(CONFIG_RB) workbench
 
-$(SSO_GENERATED): config.yml $(SSO_GENERATED_IN)
-       $(CONFIG_RB)
+$(COMPUTE_GENERATED): $(COMPUTE_GENERATED_IN)
+       $(CONFIG_RB) compute
+
+$(SSO_GENERATED): $(SSO_GENERATED_IN)
+       $(CONFIG_RB) sso
+
+$(KEEP_GENERATED): $(KEEP_GENERATED_IN)
+       $(CONFIG_RB) keep
 
 # The docker build -q option suppresses verbose build output.
 # Necessary to prevent failure on building warehouse; see
@@ -111,21 +155,35 @@ $(SSO_GENERATED): config.yml $(SSO_GENERATED_IN)
 DOCKER_BUILD = $(DOCKER) build -q --rm=true
 
 # ============================================================
-# The main Arvados servers: api, doc, workbench, warehouse
+# The main Arvados servers: api, doc, workbench, compute
 
 api-image: passenger-image $(BUILD) $(API_DEPS)
+       @echo "Building api-image"
        mkdir -p api/generated
        tar -czf api/generated/api.tar.gz -C build/services api
        $(DOCKER_BUILD) -t arvados/api api
        date >api-image
 
+shell-image: base-image $(BUILD) $(SHELL_DEPS)
+       @echo "Building shell-image"
+       mkdir -p shell/generated
+       $(DOCKER_BUILD) -t arvados/shell shell
+       date >shell-image
+
+compute-image: slurm-image $(BUILD) $(COMPUTE_DEPS)
+       @echo "Building compute-image"
+       $(DOCKER_BUILD) -t arvados/compute compute
+       date >compute-image
+
 doc-image: base-image $(BUILD) $(DOC_DEPS)
+       @echo "Building doc-image"
        mkdir -p doc/generated
        tar -czf doc/generated/doc.tar.gz -C build doc
        $(DOCKER_BUILD) -t arvados/doc doc
        date >doc-image
 
-keep-image: debian-image $(BUILD) $(KEEP_DEPS)
+keep-image: debian-arvados-image $(BUILD) $(KEEP_DEPS)
+       @echo "Building keep-image"
        $(DOCKER_BUILD) -t arvados/keep keep
        date >keep-image
 
@@ -144,16 +202,14 @@ bcbio-nextgen-image: $(BUILD) $(BASE_GENERATED) $(BCBIO_NEXTGEN_DEPS)
        date >bcbio-nextgen-image
 
 workbench-image: passenger-image $(BUILD) $(WORKBENCH_DEPS)
+       @echo "Building workbench-image"
        mkdir -p workbench/generated
        tar -czf workbench/generated/workbench.tar.gz -C build/apps workbench
        $(DOCKER_BUILD) -t arvados/workbench workbench
        date >workbench-image
 
-warehouse-image: base-image $(WAREHOUSE_DEPS)
-       $(DOCKER_BUILD) -t arvados/warehouse warehouse
-       date >warehouse-image
-
 sso-image: passenger-image $(SSO_DEPS)
+       @echo "Building sso-image"
        $(DOCKER_BUILD) -t arvados/sso sso
        date >sso-image
 
@@ -162,13 +218,31 @@ sso-image: passenger-image $(SSO_DEPS)
 # that are dependencies for every Arvados service.
 
 passenger-image: base-image
+       @echo "Building passenger-image"
        $(DOCKER_BUILD) -t arvados/passenger passenger
        date >passenger-image
 
-base-image: debian-image $(BASE_DEPS)
+slurm-image: base-image $(SLURM_DEPS)
+       @echo "Building slurm-image"
+       $(DOCKER_BUILD) -t arvados/slurm slurm
+       date >slurm-image
+
+base-image: debian-arvados-image $(BASE_DEPS)
+       @echo "Building base-image"
        $(DOCKER_BUILD) -t arvados/base base
        date >base-image
 
-debian-image:
+debian-arvados-image:
+       @echo "Building debian-arvados-image"
        ./mkimage-debootstrap.sh arvados/debian wheezy ftp://ftp.us.debian.org/debian/
-       date >debian-image
+       date >debian-arvados-image
+
+skydns-image:
+       @echo "Downloading skydns-image"
+       $(DOCKER) pull crosbymichael/skydns
+       date >skydns-image
+
+skydock-image:
+       @echo "Downloading skydock-image"
+       $(DOCKER) pull crosbymichael/skydock
+       date >skydock-image
index 5e3b1ed68dea63d20143d8419a738660fef26b3b..e7f2b7db1af625fff846f95ba93a5dd14b7ecced 100755 (executable)
@@ -14,7 +14,7 @@ def main options
   # Check that:
   #   * Docker is installed and can be found in the user's path
   #   * Docker can be run as a non-root user
-  #      - TODO: put the user is in the docker group if necessary
+  #      - TODO: put the user in the docker group if necessary
   #      - TODO: mount cgroup automatically
   #      - TODO: start the docker service if not started
 
@@ -64,15 +64,28 @@ def main options
       end
     end
 
+    print "Arvados needs to know the shell login name for the administrative user.\n"
+    print "This will also be used as the name for your git repository.\n"
+    print "\n"
+    user_name = ""
+    until is_valid_user_name? user_name
+      print "Enter a shell login name here: "
+      user_name = gets.strip
+      if not is_valid_user_name? user_name
+        print "That doesn't look like a valid shell login name. Please try again.\n"
+      end
+    end
+
     File.open 'config.yml', 'w' do |config_out|
+      config_out.write "# If a _PW or _SECRET variable is set to an empty string, a password\n"
+      config_out.write "# will be chosen randomly at build time. This is the\n"
+      config_out.write "# recommended setting.\n\n"
       config = YAML.load_file 'config.yml.example'
       config['API_AUTO_ADMIN_USER'] = admin_email_address
+      config['ARVADOS_USER_NAME'] = user_name
       config['API_HOSTNAME'] = generate_api_hostname
       config['PUBLIC_KEY_PATH'] = find_or_create_ssh_key(config['API_HOSTNAME'])
       config.each_key do |var|
-        if var.end_with?('_PW') or var.end_with?('_SECRET')
-          config[var] = rand(2**256).to_s(36)
-        end
         config_out.write "#{var}: #{config[var]}\n"
       end
     end
@@ -83,8 +96,9 @@ def main options
       docker_ok? docker_path and
       debootstrap_ok? and
       File.exists? 'config.yml'
-    warn "Building Arvados."
-    system({"DOCKER" => docker_path}, '/usr/bin/make', '-f', options[:makefile], *ARGV)
+    exit 0
+  else
+    exit 6
   end
 end
 
@@ -114,6 +128,15 @@ def is_valid_email? str
   str.match /^\S+@\S+\.\S+$/
 end
 
+# is_valid_user_name?
+#   Returns true if its arg looks like a valid unix username.
+#   This is a very very loose sanity check.
+#
+def is_valid_user_name? str
+  # borrowed from Debian's adduser (version 3.110)
+  str.match /^[_.A-Za-z0-9][-\@_.A-Za-z0-9]*\$?$/
+end
+
 # generate_api_hostname
 #   Generates a 5-character randomly chosen API hostname.
 #
@@ -221,6 +244,5 @@ if __FILE__ == $PROGRAM_NAME
       options[:makefile] = mk
     end
   end
-
   main options
 end
index ddf923736e87f2d6b570478a88ad46b7f415223a..d8bf256124eeeb387f6af39128b47b4ab35f9aac 100755 (executable)
@@ -2,6 +2,7 @@
 
 require 'yaml'
 require 'fileutils'
+require 'digest'
 
 abort 'Error: Ruby >= 1.9.3 required.' if RUBY_VERSION < '1.9.3'
 
@@ -13,10 +14,13 @@ config = YAML.load_file('config.yml')
 # be suitable for any installation.
 
 # Any _PW/_SECRET config settings represent passwords/secrets. If they
-# are blank, choose a password randomly.
-config.each_key do |var|
+# are blank, choose a password. Make sure the generated password
+# doesn't change if config.yml doesn't change. Otherwise, keys won't
+# match any more if (say) keep's files get regenerated but apiserver's
+# don't.
+config.sort.map do |var,val|
   if (var.end_with?('_PW') || var.end_with?('_SECRET')) && (config[var].nil? || config[var].empty?)
-    config[var] = rand(2**256).to_s(36)
+    config[var] = Digest::SHA1.hexdigest(`hostname` + var + config.to_yaml)
   end
 end
 
@@ -30,16 +34,21 @@ end
 # the same tree structure as in the original source. Then all
 # the files can be added to the docker container with a single ADD.
 
-Dir.glob('*/generated/*') do |stale_file|
-  File.delete(stale_file)
+if ARGV[0] and ARGV[0].length > 0
+  globdir = ARGV[0]
+else
+  globdir = '*'
 end
 
+FileUtils.rm_r Dir.glob(globdir + '/generated/*')
+
 File.umask(022)
-Dir.glob('*/*.in') do |template_file|
+Dir.glob(globdir + '/*.in') do |template_file|
   generated_dir = File.join(File.dirname(template_file), 'generated')
   Dir.mkdir(generated_dir) unless Dir.exists? generated_dir
   output_path = File.join(generated_dir, File.basename(template_file, '.in'))
-  File.open(output_path, "w") do |output|
+  output_mode = (File.stat(template_file).mode & 0100) ? 0755 : 0644
+  File.open(output_path, "w", output_mode) do |output|
     File.open(template_file) do |input|
       input.each_line do |line|
 
diff --git a/docker/compute/Dockerfile b/docker/compute/Dockerfile
new file mode 100644 (file)
index 0000000..929c136
--- /dev/null
@@ -0,0 +1,28 @@
+# Arvados compute node Docker container.
+
+FROM arvados/slurm
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+RUN apt-get update && apt-get -qqy install supervisor python-pip python-pyvcf python-gflags python-google-api-python-client python-virtualenv libattr1-dev libfuse-dev python-dev python-llfuse fuse crunchstat python-arvados-fuse cron
+
+ADD fuse.conf /etc/fuse.conf
+
+RUN /usr/local/rvm/bin/rvm-exec default gem install arvados-cli arvados
+
+# Install Docker from the Docker Inc. repository
+RUN apt-get update -qq && apt-get install -qqy iptables ca-certificates lxc apt-transport-https
+RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
+RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
+RUN apt-get update -qq && apt-get install -qqy lxc-docker
+
+RUN addgroup --gid 4005 crunch && mkdir /home/crunch && useradd --uid 4005 --gid 4005 crunch && usermod crunch -G fuse,docker && chown crunch:crunch /home/crunch
+
+# Supervisor.
+ADD supervisor.conf /etc/supervisor/conf.d/arvados.conf
+ADD ssh.sh /usr/local/bin/ssh.sh
+ADD generated/setup.sh /usr/local/bin/setup.sh
+ADD wrapdocker /usr/local/bin/wrapdocker.sh
+
+VOLUME /var/lib/docker
+# Start the supervisor.
+CMD ["/usr/bin/supervisord", "-n"]
diff --git a/docker/compute/fuse.conf b/docker/compute/fuse.conf
new file mode 100644 (file)
index 0000000..4ed21ba
--- /dev/null
@@ -0,0 +1,10 @@
+# Set the maximum number of FUSE mounts allowed to non-root users.
+# The default is 1000.
+#
+#mount_max = 1000
+
+# Allow non-root users to specify the 'allow_other' or 'allow_root'
+# mount options.
+#
+user_allow_other
+
diff --git a/docker/compute/setup.sh.in b/docker/compute/setup.sh.in
new file mode 100755 (executable)
index 0000000..e107d80
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+. /etc/profile.d/rvm.sh
+
+export ARVADOS_API_HOST=api
+export ARVADOS_API_HOST_INSECURE=yes
+export ARVADOS_API_TOKEN=@@API_SUPERUSER_SECRET@@
+
+arv node create --node {} > /tmp/node.json
+
+UUID=`grep \"uuid\" /tmp//node.json  |cut -f4 -d\"`
+PING_SECRET=`grep \"ping_secret\" /tmp//node.json  |cut -f4 -d\"`
+
+echo "*/5 * * * * root /usr/bin/curl -k -d ping_secret=$PING_SECRET https://api/arvados/v1/nodes/$UUID/ping" > /etc/cron.d/node_ping
+
+# Send a ping now
+/usr/bin/curl -k -d ping_secret=$PING_SECRET https://api/arvados/v1/nodes/$UUID/ping?ping_secret=$PING_SECRET
+
+# Just make sure /dev/fuse permissions are correct (the device appears after fuse is loaded)
+chmod 1660 /dev/fuse && chgrp fuse /dev/fuse
diff --git a/docker/compute/ssh.sh b/docker/compute/ssh.sh
new file mode 100755 (executable)
index 0000000..664414b
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+echo $ENABLE_SSH
+
+# Start ssh daemon if requested via the ENABLE_SSH env variable
+if [[ ! "$ENABLE_SSH" =~ (0|false|no|f|^$) ]]; then
+echo "STARTING"
+  /etc/init.d/ssh start
+fi
+
diff --git a/docker/compute/supervisor.conf b/docker/compute/supervisor.conf
new file mode 100644 (file)
index 0000000..f2cce3f
--- /dev/null
@@ -0,0 +1,29 @@
+[program:ssh]
+user=root
+command=/usr/local/bin/ssh.sh
+startsecs=0
+
+[program:munge]
+user=root
+command=/etc/init.d/munge start
+startsecs=0
+
+[program:slurm]
+user=root
+command=/etc/init.d/slurm-llnl start
+startsecs=0
+
+[program:cron]
+user=root
+command=/etc/init.d/cron start
+startsecs=0
+
+[program:setup]
+user=root
+command=/usr/local/bin/setup.sh
+startsecs=0
+
+[program:docker]
+user=root
+command=/usr/local/bin/wrapdocker.sh
+
diff --git a/docker/compute/wrapdocker b/docker/compute/wrapdocker
new file mode 100755 (executable)
index 0000000..cee1302
--- /dev/null
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+# Borrowed from https://github.com/jpetazzo/dind under Apache2
+# and slightly modified.
+
+# First, make sure that cgroups are mounted correctly.
+CGROUP=/sys/fs/cgroup
+: {LOG:=stdio}
+
+[ -d $CGROUP ] ||
+       mkdir $CGROUP
+
+mountpoint -q $CGROUP ||
+       mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
+               echo "Could not make a tmpfs mount. Did you use -privileged?"
+               exit 1
+       }
+
+if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
+then
+    mount -t securityfs none /sys/kernel/security || {
+        echo "Could not mount /sys/kernel/security."
+        echo "AppArmor detection and -privileged mode might break."
+    }
+fi
+
+# Mount the cgroup hierarchies exactly as they are in the parent system.
+for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
+do
+        [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
+        mountpoint -q $CGROUP/$SUBSYS ||
+                mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
+
+        # The two following sections address a bug which manifests itself
+        # by a cryptic "lxc-start: no ns_cgroup option specified" when
+        # trying to start containers withina container.
+        # The bug seems to appear when the cgroup hierarchies are not
+        # mounted on the exact same directories in the host, and in the
+        # container.
+
+        # Named, control-less cgroups are mounted with "-o name=foo"
+        # (and appear as such under /proc/<pid>/cgroup) but are usually
+        # mounted on a directory named "foo" (without the "name=" prefix).
+        # Systemd and OpenRC (and possibly others) both create such a
+        # cgroup. To avoid the aforementioned bug, we symlink "foo" to
+        # "name=foo". This shouldn't have any adverse effect.
+        echo $SUBSYS | grep -q ^name= && {
+                NAME=$(echo $SUBSYS | sed s/^name=//)
+                ln -s $SUBSYS $CGROUP/$NAME
+        }
+
+        # Likewise, on at least one system, it has been reported that
+        # systemd would mount the CPU and CPU accounting controllers
+        # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
+        # but on a directory called "cpu,cpuacct" (note the inversion
+        # in the order of the groups). This tries to work around it.
+        [ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
+done
+
+# Note: as I write those lines, the LXC userland tools cannot setup
+# a "sub-container" properly if the "devices" cgroup is not in its
+# own hierarchy. Let's detect this and issue a warning.
+grep -q :devices: /proc/1/cgroup ||
+       echo "WARNING: the 'devices' cgroup should be in its own hierarchy."
+grep -qw devices /proc/1/cgroup ||
+       echo "WARNING: it looks like the 'devices' cgroup is not mounted."
+
+# Now, close extraneous file descriptors.
+pushd /proc/self/fd >/dev/null
+for FD in *
+do
+       case "$FD" in
+       # Keep stdin/stdout/stderr
+       [012])
+               ;;
+       # Nuke everything else
+       *)
+               eval exec "$FD>&-"
+               ;;
+       esac
+done
+popd >/dev/null
+
+
+# If a pidfile is still around (for example after a container restart),
+# delete it so that docker can start.
+rm -rf /var/run/docker.pid
+
+exec docker -d
+
index 515fcbefd5121bbddb567c54aaa768b1008cc538..0765f2f8aa2c2dd6f06f2ca38f68c9696e09f6e5 100644 (file)
@@ -7,6 +7,10 @@
 # true when starting the container.
 PUBLIC_KEY_PATH:
 
+# Username for your Arvados user. This will be used as your shell login name
+# as well as the name for your git repository.
+ARVADOS_USER_NAME:
+
 # ARVADOS_DOMAIN: the Internet domain of this installation.
 # ARVADOS_DNS_SERVER: the authoritative nameserver for ARVADOS_DOMAIN.
 ARVADOS_DOMAIN:         # e.g. arvados.internal
@@ -62,8 +66,9 @@ API_SUPERUSER_SECRET:
 # change it and don't be dumb.
 POSTGRES_ROOT_PW: dummy_pw
 
-# The URL for this installation's Omniauth server.
-OMNIAUTH_URL: https://@@SSO_HOSTNAME@@.@@ARVADOS_DOMAIN@@
+# The URL of the SSO server that you want your API server to use. If
+# blank, use the sso docker container.
+OMNIAUTH_URL:
 
 # ==============================
 # Workbench settings
@@ -85,5 +90,11 @@ WORKBENCH_SECRET:
 SSO_HOSTNAME: sso
 SSO_SECRET:
 SSO_CLIENT_NAME: devsandbox
-SSO_CLIENT_APP_ID: devsandbox
-SSO_CLIENT_SECRET:
+# ==============================
+# Default to using auth.curoverse.com as SSO server
+# To use your a local Docker SSO server, set OMNIAUTH_URL and SSO_CLIENT_SECRET
+# to the empty string
+# ==============================
+OMNIAUTH_URL: https://auth.curoverse.com
+SSO_CLIENT_APP_ID: local_docker_installation
+SSO_CLIENT_SECRET: yohbai4eecohshoo1Yoot7tea9zoca9Eiz3Tajahweo9eePaeshaegh9meiye2ph
index 2cad65c52746ecfdd40cb9236440cfbb9c714e11..1b493e3a30a88c68395dee7f52413be3f9f7b6db 100644 (file)
@@ -4,7 +4,7 @@ MAINTAINER Brett Smith <brett@curoverse.com>
 # Install dependencies and set up system.
 # The FUSE packages help ensure that we can install the Python SDK (arv-mount).
 RUN /usr/bin/apt-get install -q -y python-dev python-llfuse python-pip \
-      libio-socket-ssl-perl libjson-perl liburi-perl libwww-perl \
+      libio-socket-ssl-perl libjson-perl liburi-perl libwww-perl dtrx \
       fuse libattr1-dev libfuse-dev && \
     /usr/sbin/adduser --disabled-password \
       --gecos 'Crunch execution user' crunch && \
@@ -13,7 +13,7 @@ RUN /usr/bin/apt-get install -q -y python-dev python-llfuse python-pip \
 
 # Install Arvados packages.
 RUN (find /usr/src/arvados/sdk -name '*.gem' -print0 | \
-      xargs -0rn 1 gem install) && \
+      xargs -0rn 1 /usr/local/rvm/bin/rvm-exec default gem install) && \
     cd /usr/src/arvados/services/fuse && \
     python setup.py install && \
     cd /usr/src/arvados/sdk/python && \
index ca37f10dc062666ee91d936d3161b8912061e579..cd40a72f5b236d9972c710dd30c656a21eaa1607 100644 (file)
@@ -2,13 +2,11 @@
 FROM arvados/debian:wheezy
 MAINTAINER Ward Vandewege <ward@curoverse.com>
 
-RUN echo 'deb http://apt.arvados.org/ wheezy main' > /etc/apt/sources.list.d/apt.arvados.org.list
+ADD generated/bin/keepstore /usr/local/bin/
+ADD generated/bin/keepproxy /usr/local/bin/
+ADD generated/run-keep /usr/local/bin/
 
-RUN /usr/bin/apt-key adv --keyserver pgp.mit.edu --recv 1078ECD7
-
-RUN /usr/bin/apt-get update
-
-RUN /usr/bin/apt-get install keep
+ADD generated/keep_signing_secret /etc/
 
 # Start keep
-CMD ["/usr/bin/keep"]
+CMD ["/usr/local/bin/run-keep"]
diff --git a/docker/keep/keep_signing_secret.in b/docker/keep/keep_signing_secret.in
new file mode 100644 (file)
index 0000000..e5b39c8
--- /dev/null
@@ -0,0 +1 @@
+@@KEEP_SIGNING_SECRET@@
\ No newline at end of file
diff --git a/docker/keep/run-keep.in b/docker/keep/run-keep.in
new file mode 100755 (executable)
index 0000000..a0b4cb0
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+pkf="/etc/keep_signing_secret"
+if [ -s "$pkf" ]
+then
+    permission_args="-permission-key-file=$pkf -enforce-permissions"
+else
+    permission_args=""
+fi
+
+exec keepstore $permission_args -listen=":25107" -volumes="/keep-data"
diff --git a/docker/shell/Dockerfile b/docker/shell/Dockerfile
new file mode 100644 (file)
index 0000000..1e1c883
--- /dev/null
@@ -0,0 +1,19 @@
+# Slurm node Docker container.
+
+FROM arvados/base
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+RUN apt-get update && apt-get -qqy install supervisor python-pip python-pyvcf python-gflags python-google-api-python-client python-virtualenv libattr1-dev libfuse-dev python-dev python-llfuse fuse crunchstat python-arvados-fuse cron vim
+
+ADD fuse.conf /etc/fuse.conf
+
+ADD generated/superuser_token /tmp/superuser_token
+
+RUN /usr/local/rvm/bin/rvm-exec default gem install arvados-cli arvados
+
+# Supervisor.
+ADD supervisor.conf /etc/supervisor/conf.d/arvados.conf
+ADD generated/setup.sh /usr/local/bin/setup.sh
+
+# Start the supervisor.
+CMD ["/usr/bin/supervisord", "-n"]
diff --git a/docker/shell/fuse.conf b/docker/shell/fuse.conf
new file mode 100644 (file)
index 0000000..4ed21ba
--- /dev/null
@@ -0,0 +1,10 @@
+# Set the maximum number of FUSE mounts allowed to non-root users.
+# The default is 1000.
+#
+#mount_max = 1000
+
+# Allow non-root users to specify the 'allow_other' or 'allow_root'
+# mount options.
+#
+user_allow_other
+
diff --git a/docker/shell/setup.sh.in b/docker/shell/setup.sh.in
new file mode 100755 (executable)
index 0000000..2815201
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+USER_NAME="@@ARVADOS_USER_NAME@@"
+
+useradd $USER_NAME -s /bin/bash
+mkdir /home/$USER_NAME/.ssh -p
+
+cp ~root/.ssh/authorized_keys /home/$USER_NAME/.ssh/authorized_keys
+
+# Install our token
+mkdir -p /home/$USER_NAME/.config/arvados;
+echo "ARVADOS_API_HOST=api" >> /home/$USER_NAME/.config/arvados/settings.conf
+echo "ARVADOS_API_HOST_INSECURE=yes" >> /home/$USER_NAME/.config/arvados/settings.conf
+echo "ARVADOS_API_TOKEN=$(cat /tmp/superuser_token)" >> /home/$USER_NAME/.config/arvados/settings.conf
+chmod 600 /home/$USER_NAME/.config/arvados/settings.conf
+
+chown $USER_NAME:$USER_NAME /home/$USER_NAME -R
+
+rm -f /tmp/superuser_token
+
+
diff --git a/docker/shell/superuser_token.in b/docker/shell/superuser_token.in
new file mode 100644 (file)
index 0000000..49bb34e
--- /dev/null
@@ -0,0 +1 @@
+@@API_SUPERUSER_SECRET@@
diff --git a/docker/shell/supervisor.conf b/docker/shell/supervisor.conf
new file mode 100644 (file)
index 0000000..97ad540
--- /dev/null
@@ -0,0 +1,15 @@
+[program:ssh]
+user=root
+command=/etc/init.d/ssh start
+startsecs=0
+
+[program:cron]
+user=root
+command=/etc/init.d/cron start
+startsecs=0
+
+[program:setup]
+user=root
+command=/usr/local/bin/setup.sh
+startsecs=0
+
diff --git a/docker/slurm/Dockerfile b/docker/slurm/Dockerfile
new file mode 100644 (file)
index 0000000..7a60bf6
--- /dev/null
@@ -0,0 +1,11 @@
+# Slurm node Docker container.
+
+FROM arvados/base
+MAINTAINER Ward Vandewege <ward@curoverse.com>
+
+RUN apt-get update && apt-get -q -y install slurm-llnl munge
+
+ADD munge.key /etc/munge/
+RUN chown munge:munge /etc/munge/munge.key && chmod 600 /etc/munge/munge.key
+ADD generated/slurm.conf /etc/slurm-llnl/
+
diff --git a/docker/slurm/munge.key b/docker/slurm/munge.key
new file mode 100644 (file)
index 0000000..34036a0
Binary files /dev/null and b/docker/slurm/munge.key differ
diff --git a/docker/slurm/slurm.conf.in b/docker/slurm/slurm.conf.in
new file mode 100644 (file)
index 0000000..7312a0e
--- /dev/null
@@ -0,0 +1,60 @@
+
+ControlMachine=api
+#SlurmUser=slurmd
+SlurmctldPort=6817
+SlurmdPort=6818
+AuthType=auth/munge
+#JobCredentialPrivateKey=/etc/slurm-llnl/slurm-key.pem
+#JobCredentialPublicCertificate=/etc/slurm-llnl/slurm-cert.pem
+StateSaveLocation=/tmp
+SlurmdSpoolDir=/tmp/slurmd
+SwitchType=switch/none
+MpiDefault=none
+SlurmctldPidFile=/var/run/slurmctld.pid
+SlurmdPidFile=/var/run/slurmd.pid
+ProctrackType=proctrack/pgid
+CacheGroups=0
+ReturnToService=2
+TaskPlugin=task/affinity
+#
+# TIMERS
+SlurmctldTimeout=300
+SlurmdTimeout=300
+InactiveLimit=0
+MinJobAge=300
+KillWait=30
+Waittime=0
+#
+# SCHEDULING
+SchedulerType=sched/backfill
+#SchedulerType=sched/builtin
+SchedulerPort=7321
+#SchedulerRootFilter=
+#SelectType=select/linear
+SelectType=select/cons_res
+SelectTypeParameters=CR_CPU_Memory
+FastSchedule=1
+#
+# LOGGING
+SlurmctldDebug=3
+#SlurmctldLogFile=
+SlurmdDebug=3
+#SlurmdLogFile=
+JobCompType=jobcomp/none
+#JobCompLoc=
+JobAcctGatherType=jobacct_gather/none
+#JobAcctLogfile=
+#JobAcctFrequency=
+#
+# COMPUTE NODES
+NodeName=DEFAULT
+# CPUs=8 State=UNKNOWN RealMemory=6967 Weight=6967
+PartitionName=DEFAULT MaxTime=INFINITE State=UP
+PartitionName=compute Default=YES Shared=yes
+#PartitionName=sysadmin Hidden=YES Shared=yes
+
+NodeName=compute[0-1]
+#NodeName=compute0 RealMemory=6967 Weight=6967
+
+PartitionName=compute Nodes=compute[0-1]
+PartitionName=crypto Nodes=compute[0-1]
diff --git a/docker/slurm/supervisor.conf b/docker/slurm/supervisor.conf
new file mode 100644 (file)
index 0000000..6563b54
--- /dev/null
@@ -0,0 +1,14 @@
+[program:ssh]
+user=root
+command=/usr/local/bin/ssh.sh
+startsecs=0
+
+[program:munge]
+user=root
+command=/etc/init.d/munge start
+
+[program:slurm]
+user=root
+command=/etc/init.d/slurm-llnl start
+
+
index d9dfeff23463f24fa2cf197b7cb6f3c3a66d9338..689f6561695116982253314d6dfc5f5749240d13 100644 (file)
@@ -3,16 +3,21 @@
 FROM arvados/passenger
 MAINTAINER Ward Vandewege <ward@curoverse.com>
 
+# We need graphviz for the provenance graphs
+RUN apt-get update && apt-get -qqy install graphviz
+
 # Update Arvados source
 RUN /bin/mkdir -p /usr/src/arvados/apps
 ADD generated/workbench.tar.gz /usr/src/arvados/apps/
-RUN /bin/cp /usr/src/arvados/apps/workbench/config/environments/production.rb.example /usr/src/arvados/apps/workbench/config/environments/production.rb
+ADD generated/workbench_rails_env /etc/
+RUN /bin/cp /usr/src/arvados/apps/workbench/config/environments/$(cat /etc/workbench_rails_env).rb.example /usr/src/arvados/apps/workbench/config/environments/$(cat /etc/workbench_rails_env).rb
 ADD generated/application.yml /usr/src/arvados/apps/workbench/config/application.yml
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle install --gemfile=/usr/src/arvados/apps/workbench/Gemfile && \
-    touch /usr/src/arvados/apps/workbench/log/production.log && \
-    chmod 666 /usr/src/arvados/apps/workbench/log/production.log && \
-    touch /usr/src/arvados/apps/workbench/db/production.sqlite3 && \
+RUN RAILS_ENV=$(cat /etc/workbench_rails_env) && \
+    /usr/local/rvm/bin/rvm-exec default bundle install --gemfile=/usr/src/arvados/apps/workbench/Gemfile && \
+    touch /usr/src/arvados/apps/workbench/log/$RAILS_ENV.log && \
+    chmod 666 /usr/src/arvados/apps/workbench/log/$RAILS_ENV.log && \
+    touch /usr/src/arvados/apps/workbench/db/$RAILS_ENV.sqlite3 && \
     cd /usr/src/arvados/apps/workbench && \
     /usr/local/rvm/bin/rvm-exec default bundle exec rake assets:precompile && \
     chown -R www-data:www-data /usr/src/arvados/apps/workbench
diff --git a/docker/workbench/workbench_rails_env.in b/docker/workbench/workbench_rails_env.in
new file mode 100644 (file)
index 0000000..f4f7638
--- /dev/null
@@ -0,0 +1 @@
+@@WORKBENCH_RAILS_MODE@@
\ No newline at end of file
index e84150a35d9b4064264393cdbab6e2e5312bc8f7..337d9abdefb84f403e8111c189797af9614468ec 100755 (executable)
@@ -53,14 +53,16 @@ end
 class Google::APIClient
  def discovery_document(api, version)
    api = api.to_s
-   return @discovery_documents["#{api}:#{version}"] ||=
+   discovery_uri = self.discovery_uri(api, version)
+   discovery_uri_hash = Digest::MD5.hexdigest(discovery_uri)
+   return @discovery_documents[discovery_uri_hash] ||=
      begin
        # fetch new API discovery doc if stale
-       cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json' rescue nil
+       cached_doc = File.expand_path "~/.cache/arvados/discovery-#{discovery_uri_hash}.json" rescue nil
 
        if cached_doc.nil? or not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
          response = self.execute!(:http_method => :get,
-                                  :uri => self.discovery_uri(api, version),
+                                  :uri => discovery_uri,
                                   :authenticated => false)
 
          begin
index 389ce9cc21373eb079b4be2655e8211d7aaaaa43..980ce83ae3a42c153d0a445d01fac4a3cf0c4dd4 100755 (executable)
@@ -243,8 +243,8 @@ class PipelineInstance
   end
   def self.create(attributes)
     result = $client.execute(:api_method => $arvados.pipeline_instances.create,
-                             :body => {
-                               :pipeline_instance => attributes.to_json
+                             :body_object => {
+                               :pipeline_instance => attributes
                              },
                              :authenticated => false,
                              :headers => {
@@ -262,8 +262,8 @@ class PipelineInstance
                              :parameters => {
                                :uuid => @pi[:uuid]
                              },
-                             :body => {
-                               :pipeline_instance => @attributes_to_update.to_json
+                             :body_object => {
+                               :pipeline_instance => @attributes_to_update
                              },
                              :authenticated => false,
                              :headers => {
@@ -285,6 +285,16 @@ class PipelineInstance
   def [](x)
     @pi[x]
   end
+
+  def log_stderr(msg)
+    $arv.log.create log: {
+      event_type: 'stderr',
+      object_uuid: self[:uuid],
+      owner_uuid: self[:owner_uuid],
+      properties: {"text" => msg},
+    }
+  end
+
   protected
   def initialize(j)
     @attributes_to_update = {}
@@ -328,7 +338,7 @@ class JobCache
     body = {job: no_nil_values(job)}.merge(no_nil_values(create_params))
 
     result = $client.execute(:api_method => $arvados.jobs.create,
-                             :body => body,
+                             :body_object => body,
                              :authenticated => false,
                              :headers => {
                                authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
@@ -345,19 +355,7 @@ class JobCache
       end
       msg += "Job submission was: #{body.to_json}"
 
-      $client.execute(:api_method => $arvados.logs.create,
-                      :body => {
-                        :log => {
-                          :object_uuid => pipeline[:uuid],
-                          :event_type => 'stderr',
-                          :owner_uuid => pipeline[:owner_uuid],
-                          :properties => {"text" => msg}
-                        }
-                      },
-                      :authenticated => false,
-                      :headers => {
-                        authorization: 'OAuth2 '+ENV['ARVADOS_API_TOKEN']
-                      })
+      pipeline.log_stderr(msg)
       nil
     end
   end
@@ -380,10 +378,6 @@ class WhRunPipelineInstance
     if template.match /[^-0-9a-z]/
       # Doesn't look like a uuid -- use it as a filename.
       @template = JSON.parse File.read(template), :symbolize_names => true
-      if !@template[:components]
-        abort ("#{$0}: Template loaded from #{template} " +
-               "does not have a \"components\" key")
-      end
     else
       result = $client.execute(:api_method => $arvados.pipeline_templates.get,
                                :parameters => {
@@ -422,8 +416,25 @@ class WhRunPipelineInstance
       end
     end
 
+    if not @template[:components].is_a?(Hash)
+      abort "\n#{Time.now} -- pipeline_template #{@template[:uuid]}\nSyntax error: Template missing \"components\" hash"
+    end
     @components = @template[:components].dup
 
+    bad_components = @components.each_pair.select do |cname, cspec|
+      not cspec.is_a?(Hash)
+    end
+    if bad_components.any?
+      abort "\n#{Time.now} -- pipeline_template #{@template[:uuid]}\nSyntax error: Components not specified with hashes: #{bad_components.map(&:first).join(', ')}"
+    end
+
+    bad_components = @components.each_pair.select do |cname, cspec|
+      not cspec[:script_parameters].is_a?(Hash)
+    end
+    if bad_components.any?
+      abort "\n#{Time.now} -- pipeline_template #{@template[:uuid]}\nSyntax error: Components missing \"script_parameters\" hashes: #{bad_components.map(&:first).join(', ')}"
+    end
+
     errors = []
     @components.each do |componentname, component|
       component[:script_parameters].each do |parametername, parameter|
@@ -454,16 +465,28 @@ class WhRunPipelineInstance
   end
 
   def setup_instance
-    if $options[:submit]
-      @instance ||= PipelineInstance.
-        create(:components => @components,
-              :pipeline_template_uuid => @template[:uuid],
-              :state => 'New')
+    if @instance
+      @instance[:properties][:run_options] ||= {}
+      if @options[:no_reuse]
+        # override properties of existing instance
+        @instance[:properties][:run_options][:enable_job_reuse] = false
+      else
+        # Default to "enable reuse" if not specified. (This code path
+        # can go away when old clients go away.)
+        if @instance[:properties][:run_options][:enable_job_reuse].nil?
+          @instance[:properties][:run_options][:enable_job_reuse] = true
+        end
+      end
     else
-      @instance ||= PipelineInstance.
-        create(:components => @components,
-             :pipeline_template_uuid => @template[:uuid],
-             :state => 'RunningOnClient')
+      @instance = PipelineInstance.
+        create(components: @components,
+               properties: {
+                 run_options: {
+                   enable_job_reuse: !@options[:no_reuse]
+                 }
+               },
+               pipeline_template_uuid: @template[:uuid],
+               state: ($options[:submit] ? 'RunningOnServer' : 'RunningOnClient'))
     end
     self
   end
@@ -503,7 +526,8 @@ class WhRunPipelineInstance
             # dealing with new API servers.
             :minimum_script_version => c[:minimum_script_version],
             :exclude_script_versions => c[:exclude_minimum_script_versions],
-            :find_or_create => !(@options[:no_reuse] || c[:nondeterministic]),
+            :find_or_create => (@instance[:properties][:run_options].andand[:enable_job_reuse] &&
+                                !c[:nondeterministic]),
             :filters => c[:filters]
           })
           if job
@@ -698,6 +722,18 @@ class WhRunPipelineInstance
       end
     end
   end
+
+  def abort(msg)
+    if @instance
+      if ["New", "Ready", "RunningOnClient",
+          "RunningOnServer"].include?(@instance[:state])
+        @instance[:state] = "Failed"
+        @instance.save
+      end
+      @instance.log_stderr(msg)
+    end
+    Kernel::abort(msg)
+  end
 end
 
 runner = WhRunPipelineInstance.new($options)
index 462b78c5f8a8f056e0acd21a4bc25c1651ae0fda..e7cac1816f890e87d3b2767d0930dd57f3d5562b 100755 (executable)
@@ -84,6 +84,8 @@ use File::Temp;
 use Fcntl ':flock';
 use File::Path qw( make_path );
 
+use constant EX_TEMPFAIL => 75;
+
 $ENV{"TMPDIR"} ||= "/tmp";
 unless (defined $ENV{"CRUNCH_TMP"}) {
   $ENV{"CRUNCH_TMP"} = $ENV{"TMPDIR"} . "/crunch-job";
@@ -150,17 +152,25 @@ if ($job_has_uuid)
 {
   $Job = $arv->{'jobs'}->{'get'}->execute('uuid' => $jobspec);
   if (!$force_unlock) {
+    # If some other crunch-job process has grabbed this job (or we see
+    # other evidence that the job is already underway) we exit
+    # EX_TEMPFAIL so crunch-dispatch (our parent process) doesn't
+    # mark the job as failed.
     if ($Job->{'is_locked_by_uuid'}) {
-      croak("Job is locked: " . $Job->{'is_locked_by_uuid'});
+      Log(undef, "Job is locked by " . $Job->{'is_locked_by_uuid'});
+      exit EX_TEMPFAIL;
     }
     if ($Job->{'success'} ne undef) {
-      croak("Job 'success' flag (" . $Job->{'success'} . ") is not null");
+      Log(undef, "Job 'success' flag (" . $Job->{'success'} . ") is not null");
+      exit EX_TEMPFAIL;
     }
     if ($Job->{'running'}) {
-      croak("Job 'running' flag is already set");
+      Log(undef, "Job 'running' flag is already set");
+      exit EX_TEMPFAIL;
     }
     if ($Job->{'started_at'}) {
-      croak("Job 'started_at' time is already set (" . $Job->{'started_at'} . ")");
+      Log(undef, "Job 'started_at' time is already set (" . $Job->{'started_at'} . ")");
+      exit EX_TEMPFAIL;
     }
   }
 }
@@ -273,7 +283,8 @@ if ($job_has_uuid)
   # Claim this job, and make sure nobody else does
   unless ($Job->update_attributes('is_locked_by_uuid' => $User->{'uuid'}) &&
           $Job->{'is_locked_by_uuid'} == $User->{'uuid'}) {
-    croak("Error while updating / locking job");
+    Log(undef, "Error while updating / locking job, exiting ".EX_TEMPFAIL);
+    exit EX_TEMPFAIL;
   }
   $Job->update_attributes('started_at' => scalar gmtime,
                           'running' => 1,
@@ -499,16 +510,17 @@ if (!$have_slurm)
 
 # If this job requires a Docker image, install that.
 my $docker_bin = "/usr/bin/docker.io";
-my ($docker_locator, $docker_hash);
+my ($docker_locator, $docker_stream, $docker_hash);
 if ($docker_locator = $Job->{docker_image_locator}) {
-  $docker_hash = find_docker_hash($docker_locator);
+  ($docker_stream, $docker_hash) = find_docker_image($docker_locator);
   if (!$docker_hash)
   {
     croak("No Docker image hash found from locator $docker_locator");
   }
+  $docker_stream =~ s/^\.//;
   my $docker_install_script = qq{
 if ! $docker_bin images -q --no-trunc | grep -qxF \Q$docker_hash\E; then
-    arv-get \Q$docker_locator/$docker_hash.tar\E | $docker_bin load
+    arv-get \Q$docker_locator$docker_stream/$docker_hash.tar\E | $docker_bin load
 fi
 };
   my $docker_pid = fork();
@@ -668,15 +680,13 @@ for (my $todo_ptr = 0; $todo_ptr <= $#jobstep_todo; $todo_ptr ++)
           elsif ($env_key eq "TASK_KEEPMOUNT") {
             $command .= "--env=\QTASK_KEEPMOUNT=/keep\E ";
           }
-          elsif ($env_key eq "CRUNCH_SRC") {
-            $command .= "--env=\QCRUNCH_SRC=/tmp/crunch-src\E ";
-          }
           else {
             $command .= "--env=\Q$env_key=$env_val\E ";
           }
         }
       }
       $command .= "--env=\QCRUNCH_NODE_SLOTS=$ENV{CRUNCH_NODE_SLOTS}\E ";
+      $command .= "--env=\QCRUNCH_SRC=/tmp/crunch-src\E ";
       $command .= "\Q$docker_hash\E ";
       $command .= "stdbuf --output=0 --error=0 ";
       $command .= "/tmp/crunch-src/crunch_scripts/" . $Job->{"script"};
@@ -689,7 +699,9 @@ for (my $todo_ptr = 0; $todo_ptr <= $#jobstep_todo; $todo_ptr ++)
 
     my @execargs = ('bash', '-c', $command);
     srun (\@srunargs, \@execargs, undef, $build_script_to_send);
-    exit (111);
+    # exec() failed, we assume nothing happened.
+    Log(undef, "srun() failed on build script");
+    die;
   }
   close("writer");
   if (!defined $childpid)
@@ -836,8 +848,10 @@ if ($job_has_uuid) {
                           'finished_at' => scalar gmtime)
 }
 
-if ($collated_output)
-{
+if (!$collated_output) {
+  Log(undef, "output undef");
+}
+else {
   eval {
     open(my $orig_manifest, '-|', 'arv-get', $collated_output)
         or die "failed to get collated manifest: $!";
@@ -860,7 +874,8 @@ if ($collated_output)
       'uuid' => md5_hex($stripped_manifest_text),
       'manifest_text' => $orig_manifest_text,
     });
-    $Job->update_attributes('output' => $output->{uuid});
+    Log(undef, "output " . $output->{uuid});
+    $Job->update_attributes('output' => $output->{uuid}) if $job_has_uuid;
     if ($Job->{'output_is_persistent'}) {
       $arv->{'links'}->{'create'}->execute('link' => {
         'tail_kind' => 'arvados#user',
@@ -880,7 +895,7 @@ if ($collated_output)
 Log (undef, "finish");
 
 save_meta();
-exit 0;
+exit ($Job->{'success'} ? 1 : 0);
 
 
 
@@ -1181,7 +1196,20 @@ sub fetch_block
 
   my $cmd = "arv-get \Q$hash\E";
   open($keep, '-|', $cmd) or die "fetch_block: $cmd: $!";
-  sysread($keep, $output_block, 64 * 1024 * 1024);
+  $output_block = '';
+  while (1) {
+    my $buf;
+    my $bytes = sysread($keep, $buf, 1024 * 1024);
+    if (!defined $bytes) {
+      die "reading from arv-get: $!";
+    } elsif ($bytes == 0) {
+      # sysread returns 0 at the end of the pipe.
+      last;
+    } else {
+      # some bytes were read into buf.
+      $output_block .= $buf;
+    }
+  }
   close $keep;
   return $output_block;
 }
@@ -1233,15 +1261,6 @@ sub collate_output
   }
   waitpid($pid, 0);
 
-  if ($joboutput)
-  {
-    Log (undef, "output $joboutput");
-    $Job->update_attributes('output' => $joboutput) if $job_has_uuid;
-  }
-  else
-  {
-    Log (undef, "output undef");
-  }
   return $joboutput;
 }
 
@@ -1461,19 +1480,19 @@ sub must_lock_now
   }
 }
 
-sub find_docker_hash {
-  # Given a Keep locator, search for a matching link to find the Docker hash
-  # of the stored image.
+sub find_docker_image {
+  # Given a Keep locator, check to see if it contains a Docker image.
+  # If so, return its stream name and Docker hash.
+  # If not, return undef for both values.
   my $locator = shift;
-  my $links_result = $arv->{links}->{list}->execute(
-    filters => [["head_uuid", "=", $locator],
-                ["link_class", "=", "docker_image_hash"]],
-    limit => 1);
-  my $docker_hash;
-  foreach my $link (@{$links_result->{items}}) {
-    $docker_hash = lc($link->{name});
+  if (my $image = $arv->{collections}->{get}->execute(uuid => $locator)) {
+    my @file_list = @{$image->{files}};
+    if ((scalar(@file_list) == 1) &&
+        ($file_list[0][1] =~ /^([0-9A-Fa-f]{64})\.tar$/)) {
+      return ($file_list[0][0], $1);
+    }
   }
-  return $docker_hash;
+  return (undef, undef);
 }
 
 __DATA__
similarity index 99%
rename from sdk/go/src/arvados.org/sdk/sdk.go
rename to sdk/go/arvadosclient/arvadosclient.go
index 9f1880fab18792f2891f205d84cadd5f668baffc..3d5aff7b2325b4d93e3a0afacd71c44329c42f6f 100644 (file)
@@ -1,6 +1,6 @@
 /* Simple Arvados Go SDK for communicating with API server. */
 
-package sdk
+package arvadosclient
 
 import (
        "bytes"
similarity index 95%
rename from sdk/go/src/arvados.org/sdk/sdk_test.go
rename to sdk/go/arvadosclient/arvadosclient_test.go
index 8af848f8d634142bf6e9cfea949384d95b1302cf..678c53310e359c8e8783b5652c9ad6c7bff1e3c9 100644 (file)
@@ -1,4 +1,4 @@
-package sdk
+package arvadosclient
 
 import (
        "fmt"
@@ -6,7 +6,6 @@ import (
        "net/http"
        "os"
        "os/exec"
-       "strings"
        "testing"
 )
 
@@ -21,8 +20,8 @@ var _ = Suite(&ServerRequiredSuite{})
 type ServerRequiredSuite struct{}
 
 func pythonDir() string {
-       gopath := os.Getenv("GOPATH")
-       return fmt.Sprintf("%s/../python/tests", strings.Split(gopath, ":")[0])
+       cwd, _ := os.Getwd()
+       return fmt.Sprintf("%s/../../python/tests", cwd)
 }
 
 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
diff --git a/sdk/go/go.sh b/sdk/go/go.sh
deleted file mode 100755 (executable)
index 5553567..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#! /bin/sh
-
-# Wraps the 'go' executable with some environment setup.  Sets GOPATH, creates
-# 'pkg' and 'bin' directories, automatically installs dependencies, then runs
-# the underlying 'go' executable with any command line parameters provided to
-# the script.
-
-rootdir=$(readlink -f $(dirname $0))
-GOPATH=$rootdir:$GOPATH
-export GOPATH
-
-mkdir -p $rootdir/pkg
-mkdir -p $rootdir/bin
-
-go get gopkg.in/check.v1
-
-go $*
similarity index 98%
rename from sdk/go/src/arvados.org/keepclient/hashcheck.go
rename to sdk/go/keepclient/hashcheck.go
index a585d0088833d08994b3891361623b3bfeabe78e..1f696d95b64d60f69c772063be7ae5b60e671cca 100644 (file)
@@ -30,9 +30,10 @@ type HashCheckingReader struct {
 // the checksum doesn't match.
 func (this HashCheckingReader) Read(p []byte) (n int, err error) {
        n, err = this.Reader.Read(p)
-       if err == nil {
+       if n > 0 {
                this.Hash.Write(p[:n])
-       } else if err == io.EOF {
+       }
+       if err == io.EOF {
                sum := this.Hash.Sum(make([]byte, 0, this.Hash.Size()))
                if fmt.Sprintf("%x", sum) != this.Check {
                        err = BadChecksum
similarity index 97%
rename from sdk/go/src/arvados.org/keepclient/keepclient.go
rename to sdk/go/keepclient/keepclient.go
index d43a2150f3f0950b692df0344c85983d7f4977e8..e1c25c9e1d82ee5f2f41bbb72182faf5a73b638d 100644 (file)
@@ -2,8 +2,8 @@
 package keepclient
 
 import (
-       "arvados.org/sdk"
-       "arvados.org/streamer"
+       "git.curoverse.com/arvados.git/sdk/go/streamer"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "crypto/md5"
        "errors"
        "fmt"
@@ -33,7 +33,7 @@ const X_Keep_Replicas_Stored = "X-Keep-Replicas-Stored"
 
 // Information about Arvados and Keep servers.
 type KeepClient struct {
-       Arvados       *sdk.ArvadosClient
+       Arvados       *arvadosclient.ArvadosClient
        Want_replicas int
        Using_proxy   bool
        service_roots *[]string
@@ -43,7 +43,7 @@ type KeepClient struct {
 
 // Create a new KeepClient.  This will contact the API server to discover Keep
 // servers.
-func MakeKeepClient(arv *sdk.ArvadosClient) (kc KeepClient, err error) {
+func MakeKeepClient(arv *arvadosclient.ArvadosClient) (kc KeepClient, err error) {
        kc = KeepClient{
                Arvados:       arv,
                Want_replicas: 2,
similarity index 92%
rename from sdk/go/src/arvados.org/keepclient/keepclient_test.go
rename to sdk/go/keepclient/keepclient_test.go
index 5327fb507e9b6de8a323fc9be8738d7d26b6595e..015b24896ebf72ed756509057a7f622f464a4438 100644 (file)
@@ -1,8 +1,8 @@
 package keepclient
 
 import (
-       "arvados.org/sdk"
-       "arvados.org/streamer"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
+       "git.curoverse.com/arvados.git/sdk/go/streamer"
        "crypto/md5"
        "flag"
        "fmt"
@@ -14,7 +14,6 @@ import (
        "net/http"
        "os"
        "os/exec"
-       "strings"
        "testing"
 )
 
@@ -36,20 +35,36 @@ type ServerRequiredSuite struct{}
 type StandaloneSuite struct{}
 
 func pythonDir() string {
-       gopath := os.Getenv("GOPATH")
-       return fmt.Sprintf("%s/../python/tests", strings.Split(gopath, ":")[0])
+       cwd, _ := os.Getwd()
+       return fmt.Sprintf("%s/../../python/tests", cwd)
 }
 
 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
        if *no_server {
                c.Skip("Skipping tests that require server")
-       } else {
-               os.Chdir(pythonDir())
-               if err := exec.Command("python", "run_test_server.py", "start").Run(); err != nil {
-                       panic("'python run_test_server.py start' returned error")
+               return
+       }
+       os.Chdir(pythonDir())
+       {
+               cmd := exec.Command("python", "run_test_server.py", "start")
+               stderr, err := cmd.StderrPipe()
+               if err != nil {
+                       log.Fatalf("Setting up stderr pipe: %s", err)
+               }
+               go io.Copy(os.Stderr, stderr)
+               if err := cmd.Run(); err != nil {
+                       panic(fmt.Sprintf("'python run_test_server.py start' returned error %s", err))
+               }
+       }
+       {
+               cmd := exec.Command("python", "run_test_server.py", "start_keep")
+               stderr, err := cmd.StderrPipe()
+               if err != nil {
+                       log.Fatalf("Setting up stderr pipe: %s", err)
                }
-               if err := exec.Command("python", "run_test_server.py", "start_keep").Run(); err != nil {
-                       panic("'python run_test_server.py start_keep' returned error")
+               go io.Copy(os.Stderr, stderr)
+               if err := cmd.Run(); err != nil {
+                       panic(fmt.Sprintf("'python run_test_server.py start_keep' returned error %s", err))
                }
        }
 }
@@ -65,7 +80,7 @@ func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
        os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
        os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, Equals, nil)
 
        kc, err := MakeKeepClient(&arv)
@@ -126,7 +141,7 @@ func UploadToStubHelper(c *C, st http.Handler, f func(KeepClient, string,
        listener, url := RunBogusKeepServer(st, 2990)
        defer listener.Close()
 
-       arv, _ := sdk.MakeArvadosClient()
+       arv, _ := arvadosclient.MakeArvadosClient()
        arv.ApiToken = "abc123"
 
        kc, _ := MakeKeepClient(&arv)
@@ -260,7 +275,7 @@ func (s *StandaloneSuite) TestPutB(c *C) {
                "foo",
                make(chan string, 2)}
 
-       arv, _ := sdk.MakeArvadosClient()
+       arv, _ := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 2
@@ -302,7 +317,7 @@ func (s *StandaloneSuite) TestPutHR(c *C) {
                "foo",
                make(chan string, 2)}
 
-       arv, _ := sdk.MakeArvadosClient()
+       arv, _ := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 2
@@ -356,7 +371,7 @@ func (s *StandaloneSuite) TestPutWithFail(c *C) {
        fh := FailHandler{
                make(chan string, 1)}
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 2
@@ -405,7 +420,7 @@ func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
        fh := FailHandler{
                make(chan string, 4)}
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 2
@@ -465,7 +480,7 @@ func (s *StandaloneSuite) TestGet(c *C) {
        listener, url := RunBogusKeepServer(st, 2990)
        defer listener.Close()
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots([]string{url})
@@ -491,7 +506,7 @@ func (s *StandaloneSuite) TestGetFail(c *C) {
        listener, url := RunBogusKeepServer(st, 2990)
        defer listener.Close()
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots([]string{url})
@@ -521,7 +536,7 @@ func (s *StandaloneSuite) TestChecksum(c *C) {
        listener, url := RunBogusKeepServer(st, 2990)
        defer listener.Close()
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
        arv.ApiToken = "abc123"
        kc.SetServiceRoots([]string{url})
@@ -554,7 +569,7 @@ func (s *StandaloneSuite) TestGetWithFailures(c *C) {
                "abc123",
                []byte("foo")}
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
        arv.ApiToken = "abc123"
        service_roots := make([]string, 5)
@@ -589,7 +604,7 @@ func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
        os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
        os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, err := MakeKeepClient(&arv)
        c.Assert(err, Equals, nil)
 
@@ -638,7 +653,7 @@ func (s *StandaloneSuite) TestPutProxy(c *C) {
 
        st := StubProxyHandler{make(chan string, 1)}
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 2
@@ -669,7 +684,7 @@ func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
 
        st := StubProxyHandler{make(chan string, 1)}
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, _ := MakeKeepClient(&arv)
 
        kc.Want_replicas = 3
similarity index 99%
rename from sdk/go/src/arvados.org/keepclient/support.go
rename to sdk/go/keepclient/support.go
index f3e47f92f5864d2fe3d8b1e06de92e2075f4faf9..ce15ce91adbab7926c0eca76ece43b52ab051bb2 100644 (file)
@@ -2,7 +2,7 @@
 package keepclient
 
 import (
-       "arvados.org/streamer"
+       "git.curoverse.com/arvados.git/sdk/go/streamer"
        "errors"
        "fmt"
        "io"
index 090c08e4c22a7a2138ff7778db3293b2c17f0424..105d068d4b60377ef81bcd0f4af620da766bdb68 100644 (file)
@@ -2,4 +2,4 @@
 /dist/
 /*.egg
 /*.egg-info
-/tmp
+/tests/tmp
index 42c4b3aae63b4c460fa4fc19bea3180c41fb5d9f..060ed95d959531917749584176c5f42b8c453f66 100644 (file)
@@ -25,6 +25,17 @@ from stream import *
 import errors
 import util
 
+# Set up Arvados logging based on the user's configuration.
+# All Arvados code should log under the arvados hierarchy.
+log_handler = logging.StreamHandler()
+log_handler.setFormatter(logging.Formatter(
+        '%(asctime)s %(name)s[%(process)d] %(levelname)s: %(message)s',
+        '%Y-%m-%d %H:%M:%S'))
+logger = logging.getLogger('arvados')
+logger.addHandler(log_handler)
+logger.setLevel(logging.DEBUG if config.get('ARVADOS_DEBUG')
+                else logging.WARNING)
+
 def task_set_output(self,s):
     api('v1').job_tasks().update(uuid=self['uuid'],
                                  body={
index 64c2125ed8fb39d3679f2eea379cad1e8591ca4b..1db694f762374c1ec93f9da12df272fca43caa3a 100644 (file)
@@ -8,11 +8,13 @@ import hashlib
 
 import apiclient
 import apiclient.discovery
+import apiclient.errors
 import config
 import errors
 import util
 
-services = {}
+_logger = logging.getLogger('arvados.api')
+conncache = {}
 
 class CredentialsFromToken(object):
     def __init__(self, api_token):
@@ -56,6 +58,15 @@ def _cast_objects_too(value, schema_type):
         return _cast_orig(value, schema_type)
 apiclient.discovery._cast = _cast_objects_too
 
+# Convert apiclient's HttpErrors into our own API error subclass for better
+# error reporting.
+# Reassigning apiclient.errors.HttpError is not sufficient because most of the
+# apiclient submodules import the class into their own namespace.
+def _new_http_error(cls, *args, **kwargs):
+    return super(apiclient.errors.HttpError, cls).__new__(
+        errors.ApiError, *args, **kwargs)
+apiclient.errors.HttpError.__new__ = staticmethod(_new_http_error)
+
 def http_cache(data_type):
     path = os.environ['HOME'] + '/.cache/arvados/' + data_type
     try:
@@ -64,11 +75,28 @@ def http_cache(data_type):
         path = None
     return path
 
-def api(version=None, cache=True, host=None, token=None, insecure=False):
-    global services
-
-    if 'ARVADOS_DEBUG' in config.settings():
-        logging.basicConfig(level=logging.DEBUG)
+def api(version=None, cache=True, host=None, token=None, insecure=False, **kwargs):
+    """Return an apiclient Resources object for an Arvados instance.
+
+    Arguments:
+    * version: A string naming the version of the Arvados API to use (for
+      example, 'v1').
+    * cache: If True (default), return an existing Resources object if
+      one already exists with the same endpoint and credentials. If
+      False, create a new one, and do not keep it in the cache (i.e.,
+      do not return it from subsequent api(cache=True) calls with
+      matching endpoint and credentials).
+    * host: The Arvados API server host (and optional :port) to connect to.
+    * token: The authentication token to send with each API call.
+    * insecure: If True, ignore SSL certificate validation errors.
+
+    Additional keyword arguments will be passed directly to
+    `apiclient.discovery.build` if a new Resource object is created.
+    If the `discoveryServiceUrl` or `http` keyword arguments are
+    missing, this function will set default values for them, based on
+    the current Arvados configuration settings.
+
+    """
 
     if not version:
         version = 'v1'
@@ -76,8 +104,7 @@ def api(version=None, cache=True, host=None, token=None, insecure=False):
                      "Call arvados.api('%s') instead." %
                      version)
     if host and token:
-        # Provided by caller
-        pass
+        apiinsecure = insecure
     elif not host and not token:
         # Load from user configuration or environment
         for x in ['ARVADOS_API_HOST', 'ARVADOS_API_TOKEN']:
@@ -85,8 +112,8 @@ def api(version=None, cache=True, host=None, token=None, insecure=False):
                 raise Exception("%s is not set. Aborting." % x)
         host = config.get('ARVADOS_API_HOST')
         token = config.get('ARVADOS_API_TOKEN')
-        apiinsecure = re.match(r'(?i)^(true|1|yes)$',
-                               config.get('ARVADOS_API_HOST_INSECURE', 'no'))
+        apiinsecure = (config.get('ARVADOS_API_HOST_INSECURE', '').lower() in
+                       ('yes', 'true', '1'))
     else:
         # Caller provided one but not the other
         if not host:
@@ -94,26 +121,39 @@ def api(version=None, cache=True, host=None, token=None, insecure=False):
         else:
             raise Exception("host argument provided, but token missing.")
 
-    connprofile = hashlib.sha1(' '.join([
-        version, host, token, ('y' if apiinsecure else 'n')
-    ])).hexdigest()
+    if cache:
+        connprofile = hashlib.sha1(' '.join([
+            version, host, token, ('y' if apiinsecure else 'n')
+        ])).hexdigest()
+        svc = conncache.get(connprofile)
+        if svc:
+            return svc
+
+    if 'http' not in kwargs:
+        http_kwargs = {}
+        # Prefer system's CA certificates (if available) over httplib2's.
+        certs_path = '/etc/ssl/certs/ca-certificates.crt'
+        if os.path.exists(certs_path):
+            http_kwargs['ca_certs'] = certs_path
+        if cache:
+            http_kwargs['cache'] = http_cache('discovery')
+        if apiinsecure:
+            http_kwargs['disable_ssl_certificate_validation'] = True
+        kwargs['http'] = httplib2.Http(**http_kwargs)
 
-    if not cache or not services.get(connprofile):
-        url = 'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % host
-        credentials = CredentialsFromToken(api_token=token)
+    credentials = CredentialsFromToken(api_token=token)
+    kwargs['http'] = credentials.authorize(kwargs['http'])
 
-        # Use system's CA certificates (if we find them) instead of httplib2's
-        ca_certs = '/etc/ssl/certs/ca-certificates.crt'
-        if not os.path.exists(ca_certs):
-            ca_certs = None             # use httplib2 default
+    if 'discoveryServiceUrl' not in kwargs:
+        kwargs['discoveryServiceUrl'] = (
+            'https://%s/discovery/v1/apis/{api}/{apiVersion}/rest' % (host,))
 
-        http = httplib2.Http(ca_certs=ca_certs,
-                             cache=(http_cache('discovery') if cache else None))
-        http = credentials.authorize(http)
-        if apiinsecure:
-            http.disable_ssl_certificate_validation = True
-        services[connprofile] = apiclient.discovery.build(
-            'arvados', version, http=http, discoveryServiceUrl=url)
-        http.cache = None
+    svc = apiclient.discovery.build('arvados', version, **kwargs)
+    kwargs['http'].cache = None
+    if cache:
+        conncache[connprofile] = svc
+    return svc
 
-    return services[connprofile]
+def unload_connection_cache():
+    for connprofile in conncache:
+        del conncache[connprofile]
index e420a679e246df191939343bcb1bc9533bb9c6f1..6cf4d07552183faffb74b13821289e2eae8feb42 100644 (file)
@@ -27,6 +27,8 @@ import config
 import errors
 import util
 
+_logger = logging.getLogger('arvados.collection')
+
 def normalize_stream(s, stream):
     stream_tokens = [s]
     sortedfiles = list(stream.keys())
@@ -91,10 +93,10 @@ def normalize(collection):
 
 class CollectionReader(object):
     def __init__(self, manifest_locator_or_text):
-        if re.search(r'^[a-f0-9]{32}(\+\d+)?(\+\S+)*$', manifest_locator_or_text):
+        if re.match(r'[a-f0-9]{32}(\+\d+)?(\+\S+)*$', manifest_locator_or_text):
             self._manifest_locator = manifest_locator_or_text
             self._manifest_text = None
-        elif re.search(r'^\S+( [a-f0-9]{32,}(\+\S+)*)*( \d+:\d+:\S+)+\n', manifest_locator_or_text):
+        elif re.match(r'(\S+)( [a-f0-9]{32}(\+\d+)(\+\S+)*)+( \d+:\d+:\S+)+\n', manifest_locator_or_text):
             self._manifest_text = manifest_locator_or_text
             self._manifest_locator = None
         else:
@@ -117,8 +119,8 @@ class CollectionReader(object):
                     uuid=self._manifest_locator).execute()
                 self._manifest_text = c['manifest_text']
             except Exception as e:
-                logging.warning("API lookup failed for collection %s (%s: %s)" %
-                                (self._manifest_locator, type(e), str(e)))
+                _logger.warning("API lookup failed for collection %s (%s: %s)",
+                                self._manifest_locator, type(e), str(e))
                 self._manifest_text = Keep.get(self._manifest_locator)
         self._streams = []
         for stream_line in self._manifest_text.split("\n"):
index 8af5814ddab5619a18d5416b10debd3b1d7e92bc..d3efa63a9bf4f71b0f60f35e3fac457b8b418fbf 100644 (file)
@@ -40,11 +40,13 @@ stream per filesystem directory that contains files.
 """)
 
 upload_opts.add_argument('--project-uuid', metavar='UUID', help="""
-When a Collection is made, make a Link to save it under the specified project.
+Store the collection in the specified project, instead of your Home
+project.
 """)
 
 upload_opts.add_argument('--name', help="""
-When a Collection is linked to a project, use the specified name.
+Save the collection with the specified name, rather than the default
+generic name "Saved at {time} by {username}@{host}".
 """)
 
 _group = upload_opts.add_mutually_exclusive_group()
@@ -352,31 +354,29 @@ def prep_project_link(args, stderr, project_exists=check_project_exists):
     # to create the desired project link for this Collection, or None.
     # Raises ValueError if the arguments request something impossible.
     making_collection = not (args.raw or args.stream)
-    any_link_spec = args.project_uuid or args.name
     if not making_collection:
-        if any_link_spec:
+        if args.name or args.project_uuid:
             raise ValueError("Requested a Link without creating a Collection")
         return None
-    elif not any_link_spec:
-        stderr.write(
-            "arv-put: No --project-uuid or --name specified.  This data will be cached\n"
-            "in Keep.  You will need to find this upload by its locator(s) later.\n")
-        return None
-    elif not args.project_uuid:
-        raise ValueError("--name requires --project-uuid")
-    elif not project_exists(args.project_uuid):
+    link = {'tail_uuid': args.project_uuid,
+            'link_class': 'name',
+            'name': args.name}
+    if not link['tail_uuid']:
+        link['tail_uuid'] = arvados.api('v1').users().current().execute()['uuid']
+    elif not project_exists(link['tail_uuid']):
         raise ValueError("Project {} not found".format(args.project_uuid))
-    link = {'tail_uuid': args.project_uuid, 'link_class': 'name'}
-    if args.name:
-        link['name'] = args.name
+    if not link['name']:
+        link['name'] = "Saved at {} by {}@{}".format(
+            datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"),
+            pwd.getpwuid(os.getuid()).pw_name,
+            socket.gethostname())
+        stderr.write(
+            "arv-put: No --name specified. Saving as \"%s\"\n" % link['name'])
+    link['owner_uuid'] = link['tail_uuid']
     return link
 
 def create_project_link(locator, link):
     link['head_uuid'] = locator
-    link.setdefault('name', "Collection saved by {}@{} at {}".format(
-            pwd.getpwuid(os.getuid()).pw_name,
-            socket.gethostname(),
-            datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")))
     return arvados.api('v1').links().create(body=link).execute()
 
 def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr):
@@ -398,20 +398,20 @@ def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr):
     bytes_expected = expected_bytes_for(args.paths)
 
     resume_cache = None
-    try:
-        resume_cache = ResumeCache(ResumeCache.make_path(args))
-    except (IOError, OSError, ValueError):
-        pass  # Couldn't open cache directory/file.  Continue without it.
-    except ResumeCacheConflict:
-        stdout.write(
-            "arv-put: Another process is already uploading this data.\n")
-        sys.exit(1)
+    if args.resume:
+        try:
+            resume_cache = ResumeCache(ResumeCache.make_path(args))
+        except (IOError, OSError, ValueError):
+            pass  # Couldn't open cache directory/file.  Continue without it.
+        except ResumeCacheConflict:
+            print >>stderr, "\n".join([
+                "arv-put: Another process is already uploading this data.",
+                "         Use --no-resume if this is really what you want."])
+            sys.exit(1)
 
     if resume_cache is None:
         writer = ArvPutCollectionWriter(resume_cache, reporter, bytes_expected)
     else:
-        if not args.resume:
-            resume_cache.restart()
         writer = ArvPutCollectionWriter.from_cache(
             resume_cache, reporter, bytes_expected)
 
index 85472d8cdf0bcb729933709aa7aee4d45a8df18e..1d9c77851eacde7f1f9571e697361c1a75a8849b 100644 (file)
@@ -1,5 +1,16 @@
 # errors.py - Arvados-specific exceptions.
 
+import apiclient.errors
+import json
+
+class ApiError(apiclient.errors.HttpError):
+    def _get_reason(self):
+        try:
+            return '; '.join(json.loads(self.content)['errors'])
+        except (KeyError, TypeError, ValueError):
+            return super(ApiError, self)._get_reason()
+
+
 class ArgumentError(Exception):
     pass
 class SyntaxError(Exception):
index 06f3b34410531a8a1f76bcf32c660d496f80d6cd..0dbfe6359dd18b93cb95d6154f22ec7dcbe24177 100644 (file)
@@ -8,6 +8,8 @@ import re
 import config
 import logging
 
+_logger = logging.getLogger('arvados.events')
+
 class EventClient(WebSocketClient):
     def __init__(self, url, filters, on_event):
         ssl_options = None
@@ -41,8 +43,8 @@ def subscribe(api, filters, on_event):
         ws = EventClient(url, filters, on_event)
         ws.connect()
         return ws
-    except:
-        logging.exception('')
+    except Exception:
+        _logger.exception('')
         if (ws):
-          ws.close_connection()        
+          ws.close_connection()
         raise
index 82c04ea61bed7d06734eb015469eeb2061d4c540..b5d5d7250378ad923a61343cc7f705c9b9a77f07 100644 (file)
@@ -19,7 +19,9 @@ import time
 import threading
 import timer
 import datetime
+import ssl
 
+_logger = logging.getLogger('arvados.keep')
 global_client_object = None
 
 from api import *
@@ -185,6 +187,10 @@ class KeepClient(object):
         def __init__(self, **kwargs):
             super(KeepClient.KeepWriterThread, self).__init__()
             self.args = kwargs
+            self._success = False
+
+        def success(self):
+            return self._success
 
         def run(self):
             with self.args['thread_limiter'] as limiter:
@@ -192,58 +198,66 @@ class KeepClient(object):
                     # My turn arrived, but the job has been done without
                     # me.
                     return
-                logging.debug("KeepWriterThread %s proceeding %s %s" %
-                              (str(threading.current_thread()),
-                               self.args['data_hash'],
-                               self.args['service_root']))
-                h = httplib2.Http()
-                url = self.args['service_root'] + self.args['data_hash']
-                api_token = config.get('ARVADOS_API_TOKEN')
-                headers = {'Authorization': "OAuth2 %s" % api_token}
-
-                if self.args['using_proxy']:
-                    # We're using a proxy, so tell the proxy how many copies we
-                    # want it to store
-                    headers['X-Keep-Desired-Replication'] = str(self.args['want_copies'])
-
-                try:
-                    logging.debug("Uploading to {}".format(url))
+                self.run_with_limiter(limiter)
+
+        def run_with_limiter(self, limiter):
+            _logger.debug("KeepWriterThread %s proceeding %s %s",
+                          str(threading.current_thread()),
+                          self.args['data_hash'],
+                          self.args['service_root'])
+            h = httplib2.Http(timeout=self.args.get('timeout', None))
+            url = self.args['service_root'] + self.args['data_hash']
+            api_token = config.get('ARVADOS_API_TOKEN')
+            headers = {'Authorization': "OAuth2 %s" % api_token}
+
+            if self.args['using_proxy']:
+                # We're using a proxy, so tell the proxy how many copies we
+                # want it to store
+                headers['X-Keep-Desired-Replication'] = str(self.args['want_copies'])
+
+            try:
+                _logger.debug("Uploading to {}".format(url))
+                resp, content = h.request(url.encode('utf-8'), 'PUT',
+                                          headers=headers,
+                                          body=self.args['data'])
+                if (resp['status'] == '401' and
+                    re.match(r'Timestamp verification failed', content)):
+                    body = KeepClient.sign_for_old_server(
+                        self.args['data_hash'],
+                        self.args['data'])
+                    h = httplib2.Http(timeout=self.args.get('timeout', None))
                     resp, content = h.request(url.encode('utf-8'), 'PUT',
                                               headers=headers,
-                                              body=self.args['data'])
-                    if (resp['status'] == '401' and
-                        re.match(r'Timestamp verification failed', content)):
-                        body = KeepClient.sign_for_old_server(
-                            self.args['data_hash'],
-                            self.args['data'])
-                        h = httplib2.Http()
-                        resp, content = h.request(url.encode('utf-8'), 'PUT',
-                                                  headers=headers,
-                                                  body=body)
-                    if re.match(r'^2\d\d$', resp['status']):
-                        logging.debug("KeepWriterThread %s succeeded %s %s" %
-                                      (str(threading.current_thread()),
-                                       self.args['data_hash'],
-                                       self.args['service_root']))
-                        replicas_stored = 1
-                        if 'x-keep-replicas-stored' in resp:
-                            # Tick the 'done' counter for the number of replica
-                            # reported stored by the server, for the case that
-                            # we're talking to a proxy or other backend that
-                            # stores to multiple copies for us.
-                            try:
-                                replicas_stored = int(resp['x-keep-replicas-stored'])
-                            except ValueError:
-                                pass
-                        return limiter.save_response(content.strip(), replicas_stored)
-
-                    logging.warning("Request fail: PUT %s => %s %s" %
-                                    (url, resp['status'], content))
-                except (httplib2.HttpLib2Error, httplib.HTTPException) as e:
-                    logging.warning("Request fail: PUT %s => %s: %s" %
-                                    (url, type(e), str(e)))
-
-    def __init__(self):
+                                              body=body)
+                if re.match(r'^2\d\d$', resp['status']):
+                    self._success = True
+                    _logger.debug("KeepWriterThread %s succeeded %s %s",
+                                  str(threading.current_thread()),
+                                  self.args['data_hash'],
+                                  self.args['service_root'])
+                    replicas_stored = 1
+                    if 'x-keep-replicas-stored' in resp:
+                        # Tick the 'done' counter for the number of replica
+                        # reported stored by the server, for the case that
+                        # we're talking to a proxy or other backend that
+                        # stores to multiple copies for us.
+                        try:
+                            replicas_stored = int(resp['x-keep-replicas-stored'])
+                        except ValueError:
+                            pass
+                    limiter.save_response(content.strip(), replicas_stored)
+                else:
+                    _logger.warning("Request fail: PUT %s => %s %s",
+                                    url, resp['status'], content)
+            except (httplib2.HttpLib2Error,
+                    httplib.HTTPException,
+                    ssl.SSLError) as e:
+                # When using https, timeouts look like ssl.SSLError from here.
+                # "SSLError: The write operation timed out"
+                _logger.warning("Request fail: PUT %s => %s: %s",
+                                url, type(e), str(e))
+
+    def __init__(self, **kwargs):
         self.lock = threading.Lock()
         self.service_roots = None
         self._cache_lock = threading.Lock()
@@ -251,6 +265,7 @@ class KeepClient(object):
         # default 256 megabyte cache
         self.cache_max = 256 * 1024 * 1024
         self.using_proxy = False
+        self.timeout = kwargs.get('timeout', 60)
 
     def shuffled_service_roots(self, hash):
         if self.service_roots == None:
@@ -284,7 +299,7 @@ class KeepClient(object):
                                f['service_port']))
                              for f in keep_services)
                     self.service_roots = sorted(set(roots))
-                    logging.debug(str(self.service_roots))
+                    _logger.debug(str(self.service_roots))
                 finally:
                     self.lock.release()
 
@@ -329,7 +344,7 @@ class KeepClient(object):
 
             # Remove the digits just used from the seed
             seed = seed[8:]
-        logging.debug(str(pseq))
+        _logger.debug(str(pseq))
         return pseq
 
     class CacheSlot(object):
@@ -387,8 +402,6 @@ class KeepClient(object):
             self._cache_lock.release()
 
     def get(self, locator):
-        #logging.debug("Keep.get %s" % (locator))
-
         if re.search(r',', locator):
             return ''.join(self.get(x) for x in locator.split(','))
         if 'KEEP_LOCAL_STORE' in os.environ:
@@ -396,7 +409,6 @@ class KeepClient(object):
         expect_hash = re.sub(r'\+.*', '', locator)
 
         slot, first = self.reserve_cache(expect_hash)
-        #logging.debug("%s %s %s" % (slot, first, expect_hash))
 
         if not first:
             v = slot.get()
@@ -434,23 +446,23 @@ class KeepClient(object):
     def get_url(self, url, headers, expect_hash):
         h = httplib2.Http()
         try:
-            logging.info("Request: GET %s" % (url))
+            _logger.info("Request: GET %s", url)
             with timer.Timer() as t:
                 resp, content = h.request(url.encode('utf-8'), 'GET',
                                           headers=headers)
-            logging.info("Received %s bytes in %s msec (%s MiB/sec)" % (len(content),
-                                                                        t.msecs,
-                                                                        (len(content)/(1024*1024))/t.secs))
+            _logger.info("Received %s bytes in %s msec (%s MiB/sec)",
+                         len(content), t.msecs,
+                         (len(content)/(1024*1024))/t.secs)
             if re.match(r'^2\d\d$', resp['status']):
                 m = hashlib.new('md5')
                 m.update(content)
                 md5 = m.hexdigest()
                 if md5 == expect_hash:
                     return content
-                logging.warning("Checksum fail: md5(%s) = %s" % (url, md5))
+                _logger.warning("Checksum fail: md5(%s) = %s", url, md5)
         except Exception as e:
-            logging.info("Request fail: GET %s => %s: %s" %
-                         (url, type(e), str(e)))
+            _logger.info("Request fail: GET %s => %s: %s",
+                         url, type(e), str(e))
         return None
 
     def put(self, data, **kwargs):
@@ -466,16 +478,33 @@ class KeepClient(object):
         threads = []
         thread_limiter = KeepClient.ThreadLimiter(want_copies)
         for service_root in self.shuffled_service_roots(data_hash):
-            t = KeepClient.KeepWriterThread(data=data,
-                                            data_hash=data_hash,
-                                            service_root=service_root,
-                                            thread_limiter=thread_limiter,
-                                            using_proxy=self.using_proxy,
-                                            want_copies=(want_copies if self.using_proxy else 1))
+            t = KeepClient.KeepWriterThread(
+                data=data,
+                data_hash=data_hash,
+                service_root=service_root,
+                thread_limiter=thread_limiter,
+                timeout=self.timeout,
+                using_proxy=self.using_proxy,
+                want_copies=(want_copies if self.using_proxy else 1))
             t.start()
             threads += [t]
         for t in threads:
             t.join()
+        if thread_limiter.done() < want_copies:
+            # Retry the threads (i.e., services) that failed the first
+            # time around.
+            threads_retry = []
+            for t in threads:
+                if not t.success():
+                    _logger.warning("Retrying: PUT %s %s",
+                                    t.args['service_root'],
+                                    t.args['data_hash'])
+                    retry_with_args = t.args.copy()
+                    t_retry = KeepClient.KeepWriterThread(**retry_with_args)
+                    t_retry.start()
+                    threads_retry += [t_retry]
+            for t in threads_retry:
+                t.join()
         have_copies = thread_limiter.done()
         # If we're done, return the response from Keep
         if have_copies >= want_copies:
index cbd1ba91dbf996bb6d99399606ca6d94ddbb98dc..7a29100a398cc1e6b06b0f452845079d23c34a2c 100644 (file)
@@ -1,7 +1,6 @@
 import gflags
 import httplib
 import httplib2
-import logging
 import os
 import pprint
 import sys
@@ -137,7 +136,7 @@ class StreamFileReader(object):
         data = ''
         for locator, blocksize, segmentoffset, segmentsize in locators_and_ranges(self.segments, self._filepos, size):
             data += self._stream.readfrom(locator+segmentoffset, segmentsize)
-            self._filepos += len(data)
+        self._filepos += len(data)
         return data
 
     def readfrom(self, start, size):
index 7f07e22bc09184c233ade533fd07eed03bc00126..c1b2bcbaf5a0dcb0ae2914eb7c145a31fab3efaa 100755 (executable)
@@ -8,7 +8,13 @@ import string
 import sys
 import logging
 
-logger = logging.getLogger(os.path.basename(sys.argv[0]))
+import arvados
+
+logger = logging.getLogger('arvados.arv-get')
+
+def abort(msg, code=1):
+    print >>sys.stderr, "arv-get:", msg
+    exit(code)
 
 parser = argparse.ArgumentParser(
     description='Copy data from Keep to a local file or pipe.')
@@ -87,7 +93,7 @@ if not args.r and (os.path.isdir(args.destination) or
                    args.destination[-1] == os.path.sep):
     args.destination = os.path.join(args.destination,
                                     os.path.basename(args.locator))
-    logger.debug("Appended source file name to destination directory: %s" %
+    logger.debug("Appended source file name to destination directory: %s",
                  args.destination)
 
 # Turn on --progress by default if stderr is a tty and stdout isn't.
@@ -107,8 +113,6 @@ else:
     args.destination = args.destination.rstrip(os.sep)
 
 
-import arvados
-
 r = re.search(r'^(.*?)(/.*)?$', args.locator)
 collection = r.group(1)
 get_prefix = r.group(2)
@@ -121,23 +125,22 @@ if not get_prefix:
     try:
         if not args.n:
             if not args.f and os.path.exists(args.destination):
-                logger.error('Local file %s already exists' % args.destination)
-                sys.exit(1)
+                abort('Local file %s already exists.' % (args.destination,))
             with open(args.destination, 'wb') as f:
                 try:
                     c = arvados.api('v1').collections().get(
                         uuid=collection).execute()
                     manifest = c['manifest_text']
                 except Exception as e:
-                    logging.warning(
-                        "API lookup failed for collection %s (%s: %s)" %
-                        (collection, type(e), str(e)))
+                    logger.warning(
+                        "Collection %s not found. " +
+                        "Trying to fetch directly from Keep (deprecated).",
+                        collection)
                     manifest = arvados.Keep.get(collection)
                 f.write(manifest)
         sys.exit(0)
     except arvados.errors.NotFoundError as e:
-        logger.error(e)
-        sys.exit(1)
+        abort(e)
 
 reader = arvados.CollectionReader(collection)
 
@@ -156,8 +159,7 @@ try:
                     os.path.join(s.name(), f.name())[len(get_prefix)+1:])
                 if (not (args.n or args.f or args.skip_existing) and
                     os.path.exists(dest_path)):
-                    logger.error('Local file %s already exists' % dest_path)
-                    sys.exit(1)
+                    abort('Local file %s already exists.' % (dest_path,))
             else:
                 if os.path.join(s.name(), f.name()) != '.' + get_prefix:
                     continue
@@ -165,8 +167,7 @@ try:
             todo += [(s, f, dest_path)]
             todo_bytes += f.size()
 except arvados.errors.NotFoundError as e:
-    logger.error(e)
-    sys.exit(1)
+    abort(e)
 
 # Read data, and (if not -n) write to local file(s) or pipe.
 
@@ -176,21 +177,19 @@ for s,f,outfilename in todo:
     digestor = None
     if not args.n:
         if args.skip_existing and os.path.exists(outfilename):
-            logger.debug('Local file %s exists. Skipping.' % outfilename)
+            logger.debug('Local file %s exists. Skipping.', outfilename)
             continue
         elif not args.f and (os.path.isfile(outfilename) or
                            os.path.isdir(outfilename)):
             # Good thing we looked again: apparently this file wasn't
             # here yet when we checked earlier.
-            logger.error('Local file %s already exists' % outfilename)
-            sys.exit(1)
+            abort('Local file %s already exists.' % (outfilename,))
         if args.r:
             arvados.util.mkdir_dash_p(os.path.dirname(outfilename))
         try:
             outfile = open(outfilename, 'wb')
         except Exception as e:
-            logger.error('Open(%s) failed: %s' % (outfilename, e))
-            sys.exit(1)
+            abort('Open(%s) failed: %s' % (outfilename, e))
     if args.hash:
         digestor = hashlib.new(args.hash)
     try:
index cccce59c812c9654aa9a730fce8fe0d68ca04469..ee74bb8215aa627a779c9071c9dee7abd9183d2b 100755 (executable)
@@ -6,9 +6,6 @@ import os
 import re
 import string
 import sys
-import logging
-
-logger = logging.getLogger(os.path.basename(sys.argv[0]))
 
 parser = argparse.ArgumentParser(
     description='List contents of a manifest')
@@ -24,7 +21,7 @@ args = parser.parse_args()
 
 import arvados
 
-cr = arvados.CollectionReader(arvados.Keep.get(args.locator))
+cr = arvados.CollectionReader(args.locator)
 
 for f in cr.all_files():
     if args.s:
index 0506381272c856bbc4e1b933aef6f474752d3667..478a74c22f7a1d04ccb83493bc04cd25a307ecff 100755 (executable)
@@ -6,9 +6,6 @@ import os
 import re
 import string
 import sys
-import logging
-
-logger = logging.getLogger(os.path.basename(sys.argv[0]))
 
 parser = argparse.ArgumentParser(
     description='Read manifest on standard input and put normalized manifest on standard output.')
index 58b628145ff4fe0689fc85a9833c5b7cb093d751..ce7f066ec7682ff8b4fe3cdeaa59e0656db68716 100755 (executable)
@@ -6,6 +6,8 @@ import argparse
 import arvados
 from arvados.events import subscribe
 
+logger = logging.getLogger('arvados.arv-ws')
+
 parser = argparse.ArgumentParser()
 parser.add_argument('-u', '--uuid', type=str, default="")
 args = parser.parse_args()
@@ -22,9 +24,7 @@ ws = None
 try:
   ws = subscribe(api, filters, lambda ev: on_message(ev))
   ws.run_forever()
-except KeyboardInterrupt:
-  print '' # don't log it
-except:
-  logging.exception('')
+except Exception:
+  logger.exception('')
   if (ws):
     ws.close_connection()
index dc95d8a9a5ac3f0781ed4aec9fa10d713f66479b..8ac34b8ce2b3b90a334bb08a04aa2a5c6147e541 100644 (file)
@@ -20,10 +20,13 @@ if __name__ == '__main__' and os.path.exists(
 import arvados.api
 import arvados.config
 
-ARV_API_SERVER_DIR = '../../../services/api'
-KEEP_SERVER_DIR = '../../../services/keep'
+SERVICES_SRC_DIR = os.path.join(MY_DIRNAME, '../../../services')
 SERVER_PID_PATH = 'tmp/pids/webrick-test.pid'
 WEBSOCKETS_SERVER_PID_PATH = 'tmp/pids/passenger-test.pid'
+if 'GOPATH' in os.environ:
+    gopaths = os.environ['GOPATH'].split(':')
+    gobins = [os.path.join(path, 'bin') for path in gopaths]
+    os.environ['PATH'] = ':'.join(gobins) + ':' + os.environ['PATH']
 
 def find_server_pid(PID_PATH, wait=10):
     now = time.time()
@@ -64,7 +67,7 @@ def kill_server_pid(PID_PATH, wait=10):
 
 def run(websockets=False, reuse_server=False):
     cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, ARV_API_SERVER_DIR))
+    os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
 
     if websockets:
         pid_file = WEBSOCKETS_SERVER_PID_PATH
@@ -116,7 +119,7 @@ def run(websockets=False, reuse_server=False):
 
 def stop():
     cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, ARV_API_SERVER_DIR))
+    os.chdir(os.path.join(SERVICES_SRC_DIR, 'api'))
 
     kill_server_pid(WEBSOCKETS_SERVER_PID_PATH, 0)
     kill_server_pid(SERVER_PID_PATH, 0)
@@ -135,7 +138,7 @@ def stop():
 
 def _start_keep(n, keep_args):
     keep0 = tempfile.mkdtemp()
-    keep_cmd = ["bin/keep",
+    keep_cmd = ["keepstore",
                 "-volumes={}".format(keep0),
                 "-listen=:{}".format(25107+n),
                 "-pid={}".format("tmp/keep{}.pid".format(n))]
@@ -153,15 +156,6 @@ def _start_keep(n, keep_args):
 def run_keep(blob_signing_key=None, enforce_permissions=False):
     stop_keep()
 
-    cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, KEEP_SERVER_DIR))
-    if os.environ.get('GOPATH') == None:
-        os.environ["GOPATH"] = os.getcwd()
-    else:
-        os.environ["GOPATH"] = os.getcwd() + ":" + os.environ["GOPATH"]
-
-    subprocess.call(["./go.sh", "install", "keep"])
-
     if not os.path.exists("tmp"):
         os.mkdir("tmp")
 
@@ -191,8 +185,6 @@ def run_keep(blob_signing_key=None, enforce_permissions=False):
     api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s1["uuid"] } }).execute()
     api.keep_disks().create(body={"keep_disk": {"keep_service_uuid": s2["uuid"] } }).execute()
 
-    os.chdir(cwd)
-
 def _stop_keep(n):
     kill_server_pid("tmp/keep{}.pid".format(n), 0)
     if os.path.exists("tmp/keep{}.volume".format(n)):
@@ -203,26 +195,12 @@ def _stop_keep(n):
         os.remove("tmp/keep.blob_signing_key")
 
 def stop_keep():
-    cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, KEEP_SERVER_DIR))
-
     _stop_keep(0)
     _stop_keep(1)
 
-    os.chdir(cwd)
-
 def run_keep_proxy(auth):
     stop_keep_proxy()
 
-    cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, KEEP_SERVER_DIR))
-    if os.environ.get('GOPATH') == None:
-        os.environ["GOPATH"] = os.getcwd()
-    else:
-        os.environ["GOPATH"] = os.getcwd() + ":" + os.environ["GOPATH"]
-
-    subprocess.call(["./go.sh", "install", "arvados.org/keepproxy"])
-
     if not os.path.exists("tmp"):
         os.mkdir("tmp")
 
@@ -230,7 +208,8 @@ def run_keep_proxy(auth):
     os.environ["ARVADOS_API_HOST_INSECURE"] = "true"
     os.environ["ARVADOS_API_TOKEN"] = fixture("api_client_authorizations")[auth]["api_token"]
 
-    kp0 = subprocess.Popen(["bin/keepproxy", "-pid=tmp/keepproxy.pid", "-listen=:{}".format(25101)])
+    kp0 = subprocess.Popen(["keepproxy",
+                            "-pid=tmp/keepproxy.pid", "-listen=:{}".format(25101)])
 
     authorize_with("admin")
     api = arvados.api('v1', cache=False)
@@ -238,17 +217,12 @@ def run_keep_proxy(auth):
 
     arvados.config.settings()["ARVADOS_KEEP_PROXY"] = "http://localhost:25101"
 
-    os.chdir(cwd)
-
 def stop_keep_proxy():
-    cwd = os.getcwd()
-    os.chdir(os.path.join(MY_DIRNAME, KEEP_SERVER_DIR))
     kill_server_pid("tmp/keepproxy.pid", 0)
-    os.chdir(cwd)
 
 def fixture(fix):
     '''load a fixture yaml file'''
-    with open(os.path.join(MY_DIRNAME, ARV_API_SERVER_DIR, "test", "fixtures",
+    with open(os.path.join(SERVICES_SRC_DIR, 'api', "test", "fixtures",
                            fix + ".yml")) as f:
         return yaml.load(f.read())
 
diff --git a/sdk/python/tests/test_api.py b/sdk/python/tests/test_api.py
new file mode 100644 (file)
index 0000000..4917d00
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+import apiclient.errors
+import arvados
+import httplib2
+import json
+import mimetypes
+import unittest
+import os
+import run_test_server
+
+from apiclient.http import RequestMockBuilder
+from httplib import responses as HTTP_RESPONSES
+
+if not mimetypes.inited:
+    mimetypes.init()
+
+class ArvadosApiClientTest(unittest.TestCase):
+    @classmethod
+    def response_from_code(cls, code):
+        return httplib2.Response(
+            {'status': code,
+             'reason': HTTP_RESPONSES.get(code, "Unknown Response"),
+             'Content-Type': mimetypes.types_map['.json']})
+
+    @classmethod
+    def api_error_response(cls, code, *errors):
+        return (cls.response_from_code(code),
+                json.dumps({'errors': errors,
+                            'error_token': '1234567890+12345678'}))
+
+    @classmethod
+    def setUpClass(cls):
+        run_test_server.run()
+        mock_responses = {
+            'arvados.humans.delete': (cls.response_from_code(500), ""),
+            'arvados.humans.get': cls.api_error_response(
+                422, "Bad UUID format", "Bad output format"),
+            'arvados.humans.list': (None, json.dumps(
+                    {'items_available': 0, 'items': []})),
+            }
+        req_builder = RequestMockBuilder(mock_responses)
+        cls.api = arvados.api('v1', cache=False,
+                              host=os.environ['ARVADOS_API_HOST'],
+                              token='discovery-doc-only-no-token-needed',
+                              insecure=True,
+                              requestBuilder=req_builder)
+
+    @classmethod
+    def tearDownClass(cls):
+        run_test_server.stop()
+
+    def test_basic_list(self):
+        answer = self.api.humans().list(
+            filters=[['uuid', 'is', None]]).execute()
+        self.assertEqual(answer['items_available'], len(answer['items']))
+
+    def test_exceptions_include_errors(self):
+        with self.assertRaises(apiclient.errors.HttpError) as err_ctx:
+            self.api.humans().get(uuid='xyz-xyz-abcdef').execute()
+        err_s = str(err_ctx.exception)
+        for msg in ["Bad UUID format", "Bad output format"]:
+            self.assertIn(msg, err_s)
+
+    def test_exceptions_without_errors_have_basic_info(self):
+        with self.assertRaises(apiclient.errors.HttpError) as err_ctx:
+            self.api.humans().delete(uuid='xyz-xyz-abcdef').execute()
+        self.assertIn("500", str(err_ctx.exception))
+
+
+if __name__ == '__main__':
+    unittest.main()
index 1a107a5c3d48664444c9380deef43e16cf68c29e..9bc385d2e6645cabaf140a7573c13a7705951090 100644 (file)
@@ -346,6 +346,7 @@ class ArvadosPutProjectLinkTest(ArvadosBaseTestCase):
 
     def check_link(self, link, project_uuid, link_name=None):
         self.assertEqual(project_uuid, link.get('tail_uuid'))
+        self.assertEqual(project_uuid, link.get('owner_uuid'))
         self.assertEqual('name', link.get('link_class'))
         if link_name is None:
             self.assertNotIn('name', link)
@@ -363,17 +364,24 @@ class ArvadosPutProjectLinkTest(ArvadosBaseTestCase):
         self.check_stderr_empty()
 
     def test_project_link_without_name(self):
+        username = pwd.getpwuid(os.getuid()).pw_name
         link = self.prep_link_from_arguments(['--project-uuid', self.Z_UUID])
-        self.check_link(link, self.Z_UUID)
-        self.check_stderr_empty()
-
-    def test_collection_without_project_warned(self):
-        self.assertIsNone(self.prep_link_from_arguments([]))
+        self.assertIsNotNone(link.get('name', None))
+        self.assertRegexpMatches(
+            link['name'],
+            r'^Saved at .* by {}@'.format(re.escape(username)))
+        self.check_link(link, self.Z_UUID, link.get('name', None))
         for line in self.stderr:
-            if "--project-uuid or --name" in line:
+            if "No --name specified" in line:
                 break
         else:
-            self.fail("no warning emitted about the lack of project name")
+            self.fail("no warning emitted about the lack of collection name")
+
+    @unittest.skip("prep_project_link needs an API lookup for this case")
+    def test_collection_without_project_defaults_to_home(self):
+        link = self.prep_link_from_arguments(['--name', 'test link BBB'])
+        self.check_link(link, self.Z_UUID)
+        self.check_stderr_empty()
 
     def test_no_link_or_warning_with_no_collection(self):
         self.assertIsNone(self.prep_link_from_arguments(['--raw']))
@@ -384,11 +392,6 @@ class ArvadosPutProjectLinkTest(ArvadosBaseTestCase):
                           self.prep_link_from_arguments,
                           ['--project-uuid', self.Z_UUID], False)
 
-    def test_name_without_project_is_error(self):
-        self.assertRaises(ValueError,
-                          self.prep_link_from_arguments,
-                          ['--name', 'test'])
-
     def test_link_without_collection_is_error(self):
         self.assertRaises(ValueError,
                           self.prep_link_from_arguments,
@@ -442,10 +445,6 @@ class ArvadosPutTest(ArvadosKeepLocalStoreTestCase):
             arv_put.ResumeCache.CACHE_DIR = orig_cachedir
             os.chmod(cachedir, 0o700)
 
-    def test_link_without_project_uuid_aborts(self):
-        self.assertRaises(SystemExit, self.call_main_with_args,
-                          ['--name', 'test without project UUID', '/dev/null'])
-
     def test_link_without_collection_aborts(self):
         self.assertRaises(SystemExit, self.call_main_with_args,
                           ['--name', 'test without Collection',
@@ -465,18 +464,22 @@ class ArvPutIntegrationTest(unittest.TestCase):
 
         # Use the blob_signing_key from the Rails "test" configuration
         # to provision the Keep server.
-        with open(os.path.join(os.path.dirname(__file__),
-                               run_test_server.ARV_API_SERVER_DIR,
-                               "config",
-                               "application.yml")) as f:
-            rails_config = yaml.load(f.read())
-        try:
-            config_blob_signing_key = rails_config["test"]["blob_signing_key"]
-        except KeyError:
-            config_blob_signing_key = rails_config["common"]["blob_signing_key"]
+        config_blob_signing_key = None
+        for config_file in ['application.yml', 'application.default.yml']:
+            with open(os.path.join(run_test_server.SERVICES_SRC_DIR,
+                                   "api", "config", config_file)) as f:
+                rails_config = yaml.load(f.read())
+                for config_section in ['test', 'common']:
+                    try:
+                        config_blob_signing_key = rails_config[config_section]["blob_signing_key"]
+                        break
+                    except KeyError, AttributeError:
+                        pass
+            if config_blob_signing_key != None:
+                break
         run_test_server.run()
         run_test_server.run_keep(blob_signing_key=config_blob_signing_key,
-                                 enforce_permissions=True)
+                                 enforce_permissions=(config_blob_signing_key != None))
 
     @classmethod
     def tearDownClass(cls):
@@ -490,6 +493,9 @@ class ArvPutIntegrationTest(unittest.TestCase):
                   "ARVADOS_API_TOKEN"]:
             os.environ[v] = arvados.config.settings()[v]
 
+    def current_user(self):
+        return arvados.api('v1').users().current().execute()
+
     def test_check_real_project_found(self):
         self.assertTrue(arv_put.check_project_exists(self.PROJECT_UUID),
                         "did not correctly find test fixture project")
@@ -562,29 +568,38 @@ class ArvPutIntegrationTest(unittest.TestCase):
     def run_and_find_link(self, text, extra_args=[]):
         self.authorize_with('active')
         pipe = subprocess.Popen(
-            [sys.executable, arv_put.__file__,
-             '--project-uuid', self.PROJECT_UUID] + extra_args,
+            [sys.executable, arv_put.__file__] + extra_args,
             stdin=subprocess.PIPE, stdout=subprocess.PIPE,
             stderr=subprocess.PIPE, env=self.ENVIRON)
         stdout, stderr = pipe.communicate(text)
         link_list = arvados.api('v1', cache=False).links().list(
             filters=[['head_uuid', '=', stdout.strip()],
-                     ['tail_uuid', '=', self.PROJECT_UUID],
                      ['link_class', '=', 'name']]).execute().get('items', [])
         self.assertEqual(1, len(link_list))
         return link_list[0]
 
     def test_put_collection_with_unnamed_project_link(self):
-        link = self.run_and_find_link("Test unnamed collection")
+        link = self.run_and_find_link("Test unnamed collection",
+                                      ['--project-uuid', self.PROJECT_UUID])
         username = pwd.getpwuid(os.getuid()).pw_name
         self.assertRegexpMatches(
             link['name'],
-            r'^Collection saved by {}@'.format(re.escape(username)))
+            r'^Saved at .* by {}@'.format(re.escape(username)))
+
+    def test_put_collection_with_name_and_no_project(self):
+        link_name = 'Test Collection Link in home project'
+        link = self.run_and_find_link("Test named collection in home project",
+                                      ['--name', link_name])
+        self.assertEqual(link_name, link['name'])
+        my_user_uuid = self.current_user()['uuid']
+        self.assertEqual(my_user_uuid, link['tail_uuid'])
+        self.assertEqual(my_user_uuid, link['owner_uuid'])
 
     def test_put_collection_with_named_project_link(self):
         link_name = 'Test auto Collection Link'
         link = self.run_and_find_link("Test named collection",
-                                      ['--name', link_name])
+                                      ['--name', link_name,
+                                       '--project-uuid', self.PROJECT_UUID])
         self.assertEqual(link_name, link['name'])
 
 
index 7483ba06ff696863fad19df5cc5e600b16396b9c..5354f3a9f7bf64042ab7f635119b05d112ee99a8 100644 (file)
@@ -436,6 +436,26 @@ class ArvadosCollectionsTest(ArvadosKeepLocalStoreTestCase):
         self.assertEqual(sr.readfrom(25, 5), content[25:30])
         self.assertEqual(sr.readfrom(30, 5), '')
 
+    def test_file_reader(self):
+        keepblocks = {'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+10': 'abcdefghij',
+                      'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+15': 'klmnopqrstuvwxy',
+                      'cccccccccccccccccccccccccccccccc+5': 'z0123'}
+        mk = self.MockKeep(keepblocks)
+
+        sr = arvados.StreamReader([".", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+10", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+15", "cccccccccccccccccccccccccccccccc+5", "0:10:foo", "15:10:foo"], mk)
+
+        content = 'abcdefghijpqrstuvwxy'
+
+        f = sr.files()["foo"]
+
+        f.seek(0)
+        self.assertEqual(f.read(20), content[0:20])
+
+        f.seek(0)
+        self.assertEqual(f.read(6), content[0:6])
+        self.assertEqual(f.read(6), content[6:12])
+        self.assertEqual(f.read(6), content[12:18])
+
     def test_extract_file(self):
         m1 = """. 5348b82a029fd9e971a811ce1f71360b+43 0:43:md5sum.txt
 . 085c37f02916da1cad16f93c54d899b7+41 0:41:md6sum.txt
index f5ab6148d06bcb59307f38223060c8c62550ed97..8706e2193b7c3f249db3778c03b7ee598269b364 100644 (file)
@@ -36,9 +36,10 @@ class KeepTestCase(unittest.TestCase):
 
     def test_KeepBasicRWTest(self):
         foo_locator = arvados.Keep.put('foo')
-        self.assertEqual(foo_locator,
-                         'acbd18db4cc2f85cedef654fccc4a4d8+3',
-                         'wrong md5 hash from Keep.put("foo"): ' + foo_locator)
+        self.assertRegexpMatches(
+            foo_locator,
+            '^acbd18db4cc2f85cedef654fccc4a4d8\+3',
+            'wrong md5 hash from Keep.put("foo"): ' + foo_locator)
         self.assertEqual(arvados.Keep.get(foo_locator),
                          'foo',
                          'wrong content from Keep.get(md5("foo"))')
@@ -46,10 +47,10 @@ class KeepTestCase(unittest.TestCase):
     def test_KeepBinaryRWTest(self):
         blob_str = '\xff\xfe\xf7\x00\x01\x02'
         blob_locator = arvados.Keep.put(blob_str)
-        self.assertEqual(blob_locator,
-                         '7fc7c53b45e53926ba52821140fef396+6',
-                         ('wrong locator from Keep.put(<binarydata>):' +
-                          blob_locator))
+        self.assertRegexpMatches(
+            blob_locator,
+            '^7fc7c53b45e53926ba52821140fef396\+6',
+            ('wrong locator from Keep.put(<binarydata>):' + blob_locator))
         self.assertEqual(arvados.Keep.get(blob_locator),
                          blob_str,
                          'wrong content from Keep.get(md5(<binarydata>))')
@@ -59,10 +60,10 @@ class KeepTestCase(unittest.TestCase):
         for i in range(0,23):
             blob_str = blob_str + blob_str
         blob_locator = arvados.Keep.put(blob_str)
-        self.assertEqual(blob_locator,
-                         '84d90fc0d8175dd5dcfab04b999bc956+67108864',
-                         ('wrong locator from Keep.put(<binarydata>): ' +
-                          blob_locator))
+        self.assertRegexpMatches(
+            blob_locator,
+            '^84d90fc0d8175dd5dcfab04b999bc956\+67108864',
+            ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
         self.assertEqual(arvados.Keep.get(blob_locator),
                          blob_str,
                          'wrong content from Keep.get(md5(<binarydata>))')
@@ -70,10 +71,10 @@ class KeepTestCase(unittest.TestCase):
     def test_KeepSingleCopyRWTest(self):
         blob_str = '\xff\xfe\xfd\xfc\x00\x01\x02\x03'
         blob_locator = arvados.Keep.put(blob_str, copies=1)
-        self.assertEqual(blob_locator,
-                         'c902006bc98a3eb4a3663b65ab4a6fab+8',
-                         ('wrong locator from Keep.put(<binarydata>): ' +
-                          blob_locator))
+        self.assertRegexpMatches(
+            blob_locator,
+            '^c902006bc98a3eb4a3663b65ab4a6fab\+8',
+            ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
         self.assertEqual(arvados.Keep.get(blob_locator),
                          blob_str,
                          'wrong content from Keep.get(md5(<binarydata>))')
@@ -249,9 +250,10 @@ class KeepProxyTestCase(unittest.TestCase):
         arvados.config._settings = None
 
         baz_locator = arvados.Keep.put('baz')
-        self.assertEqual(baz_locator,
-                         '73feffa4b7f6bb68e44cf984c85f6e88+3',
-                         'wrong md5 hash from Keep.put("baz"): ' + baz_locator)
+        self.assertRegexpMatches(
+            baz_locator,
+            '^73feffa4b7f6bb68e44cf984c85f6e88\+3',
+            'wrong md5 hash from Keep.put("baz"): ' + baz_locator)
         self.assertEqual(arvados.Keep.get(baz_locator),
                          'baz',
                          'wrong content from Keep.get(md5("baz"))')
@@ -271,9 +273,10 @@ class KeepProxyTestCase(unittest.TestCase):
         # keep_services/accessible
 
         baz_locator = arvados.Keep.put('baz2')
-        self.assertEqual(baz_locator,
-                         '91f372a266fe2bf2823cb8ec7fda31ce+4',
-                         'wrong md5 hash from Keep.put("baz2"): ' + baz_locator)
+        self.assertRegexpMatches(
+            baz_locator,
+            '^91f372a266fe2bf2823cb8ec7fda31ce\+4',
+            'wrong md5 hash from Keep.put("baz2"): ' + baz_locator)
         self.assertEqual(arvados.Keep.get(baz_locator),
                          'baz2',
                          'wrong content from Keep.get(md5("baz2"))')
index 429777e73f29f88128f75ae16f8494a6fed77490..6a9a52b1067f97dfa0a05a047fcf35db86b8b9ec 100644 (file)
@@ -108,17 +108,23 @@ class Arvados
   class Google::APIClient
     def discovery_document(api, version)
       api = api.to_s
-      return @discovery_documents["#{api}:#{version}"] ||=
+      discovery_uri = self.discovery_uri(api, version)
+      discovery_uri_hash = Digest::MD5.hexdigest(discovery_uri)
+      return @discovery_documents[discovery_uri_hash] ||=
         begin
           # fetch new API discovery doc if stale
-          cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json'
-          if not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
+          cached_doc = File.expand_path "~/.cache/arvados/discovery-#{discovery_uri_hash}.json" rescue nil
+          if cached_doc.nil? or not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
             response = self.execute!(:http_method => :get,
-                                     :uri => self.discovery_uri(api, version),
+                                     :uri => discovery_uri,
                                      :authenticated => false)
-            FileUtils.makedirs(File.dirname cached_doc)
-            File.open(cached_doc, 'w') do |f|
-              f.puts response.body
+            begin
+              FileUtils.makedirs(File.dirname cached_doc)
+              File.open(cached_doc, 'w') do |f|
+                f.puts response.body
+              end
+            rescue
+              return JSON.load response.body
             end
           end
 
index 7219fe4a22662c0a5650a1a53158015eb20131aa..fa2ce5ae6966e2c63adafe24d024e509d52e07b1 100644 (file)
@@ -76,3 +76,4 @@ gem 'arvados-cli', '>= 0.1.20140708213257'
 gem 'pg_power'
 
 gem 'puma'
+gem 'sshkey'
index d704ecada7dc250cd48e41f713b52f023d5b7d81..18fce168191a762672c379fe184153a60a5641b7 100644 (file)
@@ -35,13 +35,13 @@ GEM
     addressable (2.3.6)
     andand (1.3.3)
     arel (3.0.3)
-    arvados (0.1.20140708213257)
+    arvados (0.1.20140812162850)
       activesupport (>= 3.2.13)
       andand
       google-api-client (~> 0.6.3)
       json (>= 1.7.7)
       jwt (>= 0.1.5, < 1.0.0)
-    arvados-cli (0.1.20140708213257)
+    arvados-cli (0.1.20140812162850)
       activesupport (~> 3.2, >= 3.2.13)
       andand (~> 1.3, >= 1.3.3)
       arvados (~> 0.1.0)
@@ -69,7 +69,7 @@ GEM
       coffee-script-source
       execjs
     coffee-script-source (1.7.0)
-    curb (0.8.5)
+    curb (0.8.6)
     daemon_controller (1.2.0)
     database_cleaner (1.2.0)
     erubis (2.7.0)
@@ -95,7 +95,7 @@ GEM
     highline (1.6.21)
     hike (1.2.3)
     httpauth (0.2.1)
-    i18n (0.6.9)
+    i18n (0.6.11)
     journey (1.0.4)
     jquery-rails (3.1.0)
       railties (>= 3.0, < 5.0)
@@ -110,7 +110,7 @@ GEM
       mime-types (~> 1.16)
       treetop (~> 1.4.8)
     mime-types (1.25.1)
-    multi_json (1.10.0)
+    multi_json (1.10.1)
     multipart-post (1.2.0)
     net-scp (1.2.0)
       net-ssh (>= 2.6.5)
@@ -125,7 +125,7 @@ GEM
       jwt (~> 0.1.4)
       multi_json (~> 1.0)
       rack (~> 1.2)
-    oj (2.9.0)
+    oj (2.10.0)
     omniauth (1.1.1)
       hashie (~> 1.2)
       rack
@@ -192,6 +192,7 @@ GEM
       multi_json (~> 1.0)
       rack (~> 1.0)
       tilt (~> 1.1, != 1.3.0)
+    sshkey (1.6.1)
     test_after_commit (0.2.3)
     themes_for_rails (0.5.1)
       rails (>= 3.0.0)
@@ -208,7 +209,7 @@ GEM
     uglifier (2.5.0)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
-    uuidtools (2.1.4)
+    uuidtools (2.1.5)
     websocket-driver (0.3.2)
 
 PLATFORMS
@@ -236,6 +237,7 @@ DEPENDENCIES
   sass-rails (>= 3.2.0)
   simplecov (~> 0.7.1)
   simplecov-rcov
+  sshkey
   test_after_commit
   themes_for_rails
   therubyracer
index 8464a4ab5e05a515a557c5bdd9407724374642ef..d3b5c6b14725d9549deb1423ac443757366b5abe 100644 (file)
@@ -106,6 +106,7 @@ class ApplicationController < ActionController::Base
     end
     if @object and @object.errors and @object.errors.full_messages and not @object.errors.full_messages.empty?
       errors = @object.errors.full_messages
+      logger.error errors.inspect
     else
       errors = [e.inspect]
     end
@@ -202,7 +203,17 @@ class ApplicationController < ActionController::Base
       end
     end
 
-    @objects = @objects.select(@select.map { |s| "#{table_name}.#{ActiveRecord::Base.connection.quote_column_name s.to_s}" }.join ", ") if @select
+    if @select
+      # Map attribute names in @select to real column names, resolve
+      # those to fully-qualified SQL column names, and pass the
+      # resulting string to the select method.
+      api_column_map = model_class.attributes_required_columns
+      columns_list = @select.
+        flat_map { |attr| api_column_map[attr] }.
+        uniq.
+        map { |s| "#{table_name}.#{ActiveRecord::Base.connection.quote_column_name s}" }
+      @objects = @objects.select(columns_list.join(", "))
+    end
     @objects = @objects.order(@orders.join ", ") if @orders.any?
     @objects = @objects.limit(@limit)
     @objects = @objects.offset(@offset)
index 97f004ec4faf876cc1f1298ee49803fb3b601173..b65fa5b962ab76b9c26e52a0443cb4586f90a043 100644 (file)
@@ -45,16 +45,9 @@ class Arvados::V1::CollectionsController < ApplicationController
     end
 
     # Remove any permission signatures from the manifest.
-    resource_attrs[:manifest_text]
-      .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
-      word.strip!
-      loc = Locator.parse(word)
-      if loc
-        " " + loc.without_signature.to_s
-      else
-        " " + word
-      end
-    }
+    munge_manifest_locators(resource_attrs[:manifest_text]) do |loc|
+      loc.without_signature.to_s
+    end
 
     # Save the collection with the stripped manifest.
     act_as_system_user do
@@ -63,11 +56,11 @@ class Arvados::V1::CollectionsController < ApplicationController
         @object.save!
       rescue ActiveRecord::RecordNotUnique
         logger.debug resource_attrs.inspect
-        if resource_attrs[:manifest_text] and resource_attrs[:uuid]
+        if @object.manifest_text and @object.uuid
           @existing_object = model_class.
             where('uuid=? and manifest_text=?',
-                  resource_attrs[:uuid],
-                  resource_attrs[:manifest_text]).
+                  @object.uuid,
+                  @object.manifest_text).
             first
           @object = @existing_object || @object
         end
@@ -91,24 +84,13 @@ class Arvados::V1::CollectionsController < ApplicationController
   end
 
   def show
-    if current_api_client_authorization
-      signing_opts = {
-        key: Rails.configuration.blob_signing_key,
-        api_token: current_api_client_authorization.api_token,
-        ttl: Rails.configuration.blob_signing_ttl,
-      }
-      @object[:manifest_text]
-        .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
-        word.strip!
-        loc = Locator.parse(word)
-        if loc
-          " " + Blob.sign_locator(word, signing_opts)
-        else
-          " " + word
-        end
-      }
-    end
-    render json: @object.as_api_response(:with_data)
+    sign_manifests(@object[:manifest_text])
+    super
+  end
+
+  def index
+    sign_manifests(*@objects.map { |c| c[:manifest_text] })
+    super
   end
 
   def collection_uuid(uuid)
@@ -149,7 +131,7 @@ class Arvados::V1::CollectionsController < ApplicationController
 
     logger.debug "visiting #{uuid}"
 
-    if m  
+    if m
       # uuid is a collection
       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
         visited[uuid] = c.as_api_response
@@ -166,7 +148,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       Job.readable_by(current_user).where(log: uuid).each do |job|
         generate_provenance_edges(visited, job.uuid)
       end
-      
+
     else
       # uuid is something else
       rsc = ArvadosModel::resource_class_for_uuid uuid
@@ -208,7 +190,7 @@ class Arvados::V1::CollectionsController < ApplicationController
 
     logger.debug "visiting #{uuid}"
 
-    if m  
+    if m
       # uuid is a collection
       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
         visited[uuid] = c.as_api_response
@@ -226,7 +208,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
         generate_used_by_edges(visited, job.uuid)
       end
-      
+
     else
       # uuid is something else
       rsc = ArvadosModel::resource_class_for_uuid uuid
@@ -258,7 +240,27 @@ class Arvados::V1::CollectionsController < ApplicationController
     render json: visited
   end
 
+  def self.munge_manifest_locators(manifest)
+    # Given a manifest text and a block, yield each locator,
+    # and replace it with whatever the block returns.
+    manifest.andand.gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) do |word|
+      if loc = Locator.parse(word.strip)
+        " " + yield(loc)
+      else
+        " " + word
+      end
+    end
+  end
+
   protected
+
+  def find_objects_for_index
+    # Omit manifest_text from index results unless expressly selected.
+    @select ||= model_class.api_accessible_attributes(:user).
+      map { |attr_spec| attr_spec.first.to_s } - ["manifest_text"]
+    super
+  end
+
   def find_object_by_uuid
     super
     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
@@ -277,4 +279,23 @@ class Arvados::V1::CollectionsController < ApplicationController
       end
     end
   end
+
+  def munge_manifest_locators(manifest, &block)
+    self.class.munge_manifest_locators(manifest, &block)
+  end
+
+  def sign_manifests(*manifests)
+    if current_api_client_authorization
+      signing_opts = {
+        key: Rails.configuration.blob_signing_key,
+        api_token: current_api_client_authorization.api_token,
+        ttl: Rails.configuration.blob_signing_ttl,
+      }
+      manifests.each do |text|
+        munge_manifest_locators(text) do |loc|
+          Blob.sign_locator(loc.to_s, signing_opts)
+        end
+      end
+    end
+  end
 end
index da82e81ef81374e39994b08a27518e3f60de84e6..17be40a57ee449a6a0902409ed1aa9ae7853be8a 100644 (file)
@@ -3,15 +3,64 @@ class Arvados::V1::GroupsController < ApplicationController
   def self._contents_requires_parameters
     _index_requires_parameters.
       merge({
+              uuid: {
+                type: 'string', required: false, default: nil
+              },
               include_linked: {
                 type: 'boolean', required: false, default: false
               },
             })
   end
 
+  def render_404_if_no_object
+    if params[:action] == 'contents'
+      if !params[:uuid]
+        # OK!
+        @object = nil
+        true
+      elsif @object
+        # Project group
+        true
+      elsif (@object = User.where(uuid: params[:uuid]).first)
+        # "Home" pseudo-project
+        true
+      else
+        super
+      end
+    else
+      super
+    end
+  end
+
   def contents
+    # Set @objects:
+    load_searchable_objects(owner_uuid: @object.andand.uuid,
+                            include_linked: params[:include_linked])
+    sql = 'link_class=? and head_uuid in (?)'
+    sql_params = ['name', @objects.collect(&:uuid)]
+    if @object
+      sql += ' and tail_uuid=?'
+      sql_params << @object.uuid
+    end
+    @links = Link.where sql, *sql_params
+    @object_list = {
+      :kind  => "arvados#objectList",
+      :etag => "",
+      :self_link => "",
+      :links => @links.as_api_response(nil),
+      :offset => @offset,
+      :limit => @limit,
+      :items_available => @items_available,
+      :items => @objects.as_api_response(nil)
+    }
+    render json: @object_list
+  end
+
+  protected
+
+  def load_searchable_objects opts
     all_objects = []
-    all_available = 0
+    @items_available = 0
 
     # Trick apply_where_limit_order_params into applying suitable
     # per-table values. *_all are the real ones we'll apply to the
@@ -25,39 +74,41 @@ class Arvados::V1::GroupsController < ApplicationController
      Collection,
      Human, Specimen, Trait].each do |klass|
       @objects = klass.readable_by(*@read_users)
-      cond_sql = "#{klass.table_name}.owner_uuid = ?"
-      cond_params = [@object.uuid]
-      if params[:include_linked]
-        cond_sql += " OR #{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'} AND links.tail_uuid=#{klass.sanitize @object.uuid})"
+      if klass == Group
+        @objects = @objects.where(group_class: 'project')
+      end
+      if opts[:owner_uuid]
+        conds = []
+        cond_params = []
+        conds << "#{klass.table_name}.owner_uuid = ?"
+        cond_params << opts[:owner_uuid]
+        if opts[:include_linked]
+          haslink = "#{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'}"
+          haslink += " AND links.tail_uuid=#{klass.sanitize opts[:owner_uuid]}"
+          haslink += ")"
+          conds << haslink
+        end
+        if conds.any?
+          cond_sql = '(' + conds.join(') OR (') + ')'
+          @objects = @objects.where(cond_sql, *cond_params)
+        end
       end
-      @objects = @objects.where(cond_sql, *cond_params).order("#{klass.table_name}.uuid")
+
+      @objects = @objects.order("#{klass.table_name}.uuid")
       @limit = limit_all - all_objects.count
       apply_where_limit_order_params
-      items_available = @objects.
+      klass_items_available = @objects.
         except(:limit).except(:offset).
         count(:id, distinct: true)
-      all_available += items_available
-      @offset = [@offset - items_available, 0].max
+      @items_available += klass_items_available
+      @offset = [@offset - klass_items_available, 0].max
 
       all_objects += @objects.to_a
     end
-    @objects = all_objects || []
-    @links = Link.where('link_class=? and tail_uuid=?'\
-                        ' and head_uuid in (?)',
-                        'name',
-                        @object.uuid,
-                        @objects.collect(&:uuid))
-    @object_list = {
-      :kind  => "arvados#objectList",
-      :etag => "",
-      :self_link => "",
-      :links => @links.as_api_response(nil),
-      :offset => offset_all,
-      :limit => limit_all,
-      :items_available => all_available,
-      :items => @objects.as_api_response(nil)
-    }
-    render json: @object_list
+
+    @objects = all_objects
+    @limit = limit_all
+    @offset = offset_all
   end
 
 end
index 303943310b2fa00cf8dc6e2bfc9fd571fe6292d5..69778293a4096138cba2607599a2c9d47efafe6b 100644 (file)
@@ -67,18 +67,20 @@ class Arvados::V1::JobsController < ApplicationController
         if j.nondeterministic != true and
             ((j.success == true and j.output != nil) or j.running == true) and
             j.script_parameters == resource_attrs[:script_parameters]
-          if j.running
+          if j.running && j.owner_uuid == current_user.uuid
             # We'll use this if we don't find a job that has completed
             incomplete_job ||= j
           else
-            # Record the first job in the list
-            if !@object
-              @object = j
-            end
-            # Ensure that all candidate jobs actually did produce the same output
-            if @object.output != j.output
-              @object = nil
-              break
+            if Collection.readable_by(current_user).find_by_uuid(j.output)
+              # Record the first job in the list
+              if !@object
+                @object = j
+              end
+              # Ensure that all candidate jobs actually did produce the same output
+              if @object.output != j.output
+                @object = nil
+                break
+              end
             end
           end
         end
@@ -160,6 +162,24 @@ class Arvados::V1::JobsController < ApplicationController
     index
   end
 
+  def self._create_requires_parameters
+    (super rescue {}).
+      merge({
+              find_or_create: {
+                type: 'boolean', required: false, default: false
+              },
+              filters: {
+                type: 'array', required: false
+              },
+              minimum_script_version: {
+                type: 'string', required: false
+              },
+              exclude_script_versions: {
+                type: 'array', required: false
+              },
+            })
+  end
+
   def self._queue_requires_parameters
     self._index_requires_parameters
   end
index 8b45c56bcbfe576eb921ae8e31c8613388793903..0452c523bf3a21348264350c8f9deba96bfefedd 100644 (file)
@@ -7,6 +7,7 @@ class Arvados::V1::RepositoriesController < ApplicationController
     User.includes(:authorized_keys).all.each do |u|
       @users[u.uuid] = u
     end
+    admins = @users.select { |k,v| v.is_admin }
     @user_aks = {}
     @repo_info = {}
     @repos = Repository.includes(:permissions).all
@@ -17,7 +18,9 @@ class Arvados::V1::RepositoriesController < ApplicationController
         if ArvadosModel::resource_class_for_uuid(perm.tail_uuid) == Group
           @users.each do |user_uuid, user|
             user.group_permissions.each do |group_uuid, perm_mask|
-              if perm_mask[:write]
+              if perm_mask[:manage]
+                perms << {name: 'can_manage', user_uuid: user_uuid}
+              elsif perm_mask[:write]
                 perms << {name: 'can_write', user_uuid: user_uuid}
               elsif perm_mask[:read]
                 perms << {name: 'can_read', user_uuid: user_uuid}
@@ -29,10 +32,8 @@ class Arvados::V1::RepositoriesController < ApplicationController
         end
       end
       # Owner of the repository, and all admins, can RW
-      ([repo.owner_uuid] + @users.keys).each do |user_uuid|
-        %w(can_read can_write).each do |name|
-          perms << {name: name, user_uuid: user_uuid}
-        end
+      ([repo.owner_uuid] + admins.keys).each do |user_uuid|
+        perms << {name: 'can_write', user_uuid: user_uuid}
       end
       perms.each do |perm|
         user_uuid = perm[:user_uuid]
@@ -58,7 +59,11 @@ class Arvados::V1::RepositoriesController < ApplicationController
     end
     @repo_info.values.each do |repo_users|
       repo_users[:user_permissions].each do |user_uuid,perms|
-        if perms['can_write']
+        if perms['can_manage']
+          perms[:gitolite_permissions] = 'RW'
+          perms['can_write'] = true
+          perms['can_read'] = true
+        elsif perms['can_write']
           perms[:gitolite_permissions] = 'RW'
           perms['can_read'] = true
         elsif perms['can_read']
index 4470291504b87b0ef436cc99c63a087dfad632f0..a2a5759c739bc1af3e83b586bd8dff96f1268e79 100644 (file)
@@ -332,32 +332,37 @@ class Arvados::V1::SchemaController < ApplicationController
           }.compact.first
           if httpMethod and
               route.defaults[:controller] == 'arvados/v1/' + k.to_s.underscore.pluralize and
-              !d_methods[action.to_sym] and
-              ctl_class.action_methods.include? action and
-              ![:show, :index, :destroy].include?(action.to_sym)
-            method = {
-              id: "arvados.#{k.to_s.underscore.pluralize}.#{action}",
-              path: route.path.spec.to_s.sub('/arvados/v1/','').sub('(.:format)','').sub(/:(uu)?id/,'{uuid}'),
-              httpMethod: httpMethod,
-              description: "#{route.defaults[:action]} #{k.to_s.underscore.pluralize}",
-              parameters: {},
-              response: {
-                "$ref" => (action == 'index' ? "#{k.to_s}List" : k.to_s)
-              },
-              scopes: [
-                       "https://api.clinicalfuture.com/auth/arvados"
-                      ]
-            }
-            route.segment_keys.each do |key|
-              if key != :format
-                key = :uuid if key == :id
-                method[:parameters][key] = {
-                  type: "string",
-                  description: "",
-                  required: true,
-                  location: "path"
-                }
+              ctl_class.action_methods.include? action
+            if !d_methods[action.to_sym]
+              method = {
+                id: "arvados.#{k.to_s.underscore.pluralize}.#{action}",
+                path: route.path.spec.to_s.sub('/arvados/v1/','').sub('(.:format)','').sub(/:(uu)?id/,'{uuid}'),
+                httpMethod: httpMethod,
+                description: "#{action} #{k.to_s.underscore.pluralize}",
+                parameters: {},
+                response: {
+                  "$ref" => (action == 'index' ? "#{k.to_s}List" : k.to_s)
+                },
+                scopes: [
+                         "https://api.clinicalfuture.com/auth/arvados"
+                        ]
+              }
+              route.segment_keys.each do |key|
+                if key != :format
+                  key = :uuid if key == :id
+                  method[:parameters][key] = {
+                    type: "string",
+                    description: "",
+                    required: true,
+                    location: "path"
+                  }
+                end
               end
+            else
+              # We already built a generic method description, but we
+              # might find some more required parameters through
+              # introspection.
+              method = d_methods[action.to_sym]
             end
             if ctl_class.respond_to? "_#{action}_requires_parameters".to_sym
               ctl_class.send("_#{action}_requires_parameters".to_sym).each do |k, v|
@@ -378,7 +383,7 @@ class Arvados::V1::SchemaController < ApplicationController
                 end
               end
             end
-            d_methods[route.defaults[:action].to_sym] = method
+            d_methods[action.to_sym] = method
           end
         end
       end
index a044fb7bfd3ff325cf9678375af2e551d52f78ae..271299b6c97608c68b14d0500e86d779ba610b3d 100644 (file)
@@ -118,7 +118,21 @@ class Arvados::V1::UsersController < ApplicationController
 
   def self._setup_requires_parameters
     {
-      send_notification_email: { type: 'boolean', required: true },
+      user: {
+        type: 'object', required: false
+      },
+      openid_prefix: {
+        type: 'string', required: false
+      },
+      repo_name: {
+        type: 'string', required: false
+      },
+      vm_uuid: {
+        type: 'string', required: false
+      },
+      send_notification_email: {
+        type: 'boolean', required: false, default: false
+      },
     }
   end
 
diff --git a/services/api/app/mailers/profile_notifier.rb b/services/api/app/mailers/profile_notifier.rb
new file mode 100644 (file)
index 0000000..13e3b34
--- /dev/null
@@ -0,0 +1,8 @@
+class ProfileNotifier < ActionMailer::Base
+  default from: Rails.configuration.admin_notifier_email_from
+
+  def profile_created(user, address)
+    @user = user
+    mail(to: address, subject: "Profile created by #{@user.email}")
+  end
+end
index 5cd0c77c496b973e90026d6d77d25ccff3991b72..539f69d6cabd5afac1e0d3e4b19a8337bb231897 100644 (file)
@@ -68,6 +68,28 @@ class ArvadosModel < ActiveRecord::Base
     self.columns.select { |col| col.name == attr.to_s }.first
   end
 
+  def self.attributes_required_columns
+    # This method returns a hash.  Each key is the name of an API attribute,
+    # and it's mapped to a list of database columns that must be fetched
+    # to generate that attribute.
+    # This implementation generates a simple map of attributes to
+    # matching column names.  Subclasses can override this method
+    # to specify that method-backed API attributes need to fetch
+    # specific columns from the database.
+    all_columns = columns.map(&:name)
+    api_column_map = Hash.new { |hash, key| hash[key] = [] }
+    methods.grep(/^api_accessible_\w+$/).each do |method_name|
+      next if method_name == :api_accessible_attributes
+      send(method_name).each_pair do |api_attr_name, col_name|
+        col_name = col_name.to_s
+        if all_columns.include?(col_name)
+          api_column_map[api_attr_name.to_s] |= [col_name]
+        end
+      end
+    end
+    api_column_map
+  end
+
   # Return nil if current user is not allowed to see the list of
   # writers. Otherwise, return a list of user_ and group_uuids with
   # write permission. (If not returning nil, current_user is always in
@@ -146,6 +168,12 @@ class ArvadosModel < ActiveRecord::Base
         sql_params += [uuid_list]
       end
 
+      if sql_table == "collections" and users_list.any?
+        # There is a 'name' link going from a readable group to the collection.
+        name_links = "(SELECT head_uuid FROM links WHERE link_class='name' AND tail_uuid IN (#{sanitized_uuid_list}))"
+        sql_conds += ["#{sql_table}.uuid IN #{name_links}"]
+      end
+
       # Link head points to this row, or to the owner of this row (the thing to be read)
       #
       # Link tail originates from this user, or a group that is readable by this
index 5856e0c8e8d9a6a2c0b7e0edb9c01e782b3dfc77..b156a1d0f697440ae0912ff352049bdf0de28c2a 100644 (file)
@@ -33,13 +33,14 @@ class AuthorizedKey < ArvadosModel
 
   def public_key_must_be_unique
     if self.public_key
-      key = /^ssh-(rsa|dss) [A-Za-z0-9+\/=\+]+\b/.match(self.public_key)
-      
-      if not key
+      #key = /^ssh-(rsa|dss) [A-Za-z0-9+\/=\+]+\b/.match(self.public_key)
+      valid_key = SSHKey.valid_ssh_public_key? self.public_key
+
+      if not valid_key
         errors.add(:public_key, "does not appear to be a valid ssh-rsa or dsa public key")
       else
         # Valid if no other rows have this public key
-        if self.class.where('public_key like ?', "%#{key[0]}%").any?
+        if self.class.where('public_key like ?', "%#{self.public_key}%").any?
           errors.add(:public_key, "already exists in the database, use a different key.")
           return false
         end
index e9be28b46244a9249c806177cc8d88d606e0a112..ac845f50adc71e5e95d4eded09779be72ffdca46 100644 (file)
@@ -6,10 +6,13 @@ class Collection < ArvadosModel
   api_accessible :user, extend: :common do |t|
     t.add :data_size
     t.add :files
+    t.add :manifest_text
   end
 
-  api_accessible :with_data, extend: :user do |t|
-    t.add :manifest_text
+  def self.attributes_required_columns
+    super.merge({ "data_size" => ["manifest_text"],
+                  "files" => ["manifest_text"],
+                })
   end
 
   def redundancy_status
@@ -29,26 +32,38 @@ class Collection < ArvadosModel
   end
 
   def assign_uuid
-    if self.manifest_text.nil? and self.uuid.nil?
-      super
-    elsif self.manifest_text and self.uuid
+    if not self.manifest_text
+      errors.add :manifest_text, 'not supplied'
+      return false
+    end
+    expect_uuid = Digest::MD5.hexdigest(self.manifest_text)
+    if self.uuid
       self.uuid.gsub! /\+.*/, ''
-      if self.uuid == Digest::MD5.hexdigest(self.manifest_text)
-        self.uuid.gsub! /$/, '+' + self.manifest_text.length.to_s
-        true
-      else
-        errors.add :uuid, 'does not match checksum of manifest_text'
-        false
+      if self.uuid != expect_uuid
+        errors.add :uuid, 'must match checksum of manifest_text'
+        return false
       end
-    elsif self.manifest_text
-      errors.add :uuid, 'not supplied (must match checksum of manifest_text)'
-      false
     else
-      errors.add :manifest_text, 'not supplied'
-      false
+      self.uuid = expect_uuid
     end
+    self.uuid.gsub! /$/, '+' + self.manifest_text.length.to_s
+    true
   end
 
+  # TODO (#3036/tom) replace above assign_uuid method with below assign_uuid and self.generate_uuid
+  # def assign_uuid
+  #   # Even admins cannot assign collection uuids.
+  #   self.uuid = self.class.generate_uuid
+  # end
+  # def self.generate_uuid
+  #   # The last 10 characters of a collection uuid are the last 10
+  #   # characters of the base-36 SHA256 digest of manifest_text.
+  #   [Server::Application.config.uuid_prefix,
+  #    self.uuid_prefix,
+  #    rand(2**256).to_s(36)[-5..-1] + Digest::SHA256.hexdigest(self.manifest_text).to_i(16).to_s(36)[-10..-1],
+  #   ].join '-'
+  # end
+
   def data_size
     inspect_manifest_text if @data_size.nil? or manifest_text_changed?
     @data_size
@@ -66,15 +81,6 @@ class Collection < ArvadosModel
       return
     end
 
-    #normalized_manifest = ""
-    #IO.popen(['arv-normalize'], 'w+b') do |io|
-    #  io.write manifest_text
-    #  io.close_write
-    #  while buf = io.read(2**20)
-    #    normalized_manifest += buf
-    #  end
-    #end
-
     @data_size = 0
     tmp = {}
 
@@ -156,12 +162,15 @@ class Collection < ArvadosModel
       joins("JOIN collections ON links.head_uuid = collections.uuid").
       order("links.created_at DESC")
 
-    # If the search term is a Collection locator with an associated
-    # Docker image hash link, return that Collection.
-    coll_matches = base_search.
-      where(link_class: "docker_image_hash", collections: {uuid: search_term})
-    if match = coll_matches.first
-      return [match.head_uuid]
+    # If the search term is a Collection locator that contains one file
+    # that looks like a Docker image, return it.
+    if loc = Locator.parse(search_term)
+      loc.strip_hints!
+      coll_match = readable_by(*readers).where(uuid: loc.to_s).first
+      if coll_match and (coll_match.files.size == 1) and
+          (coll_match.files[0][1] =~ /^[0-9A-Fa-f]{64}\.tar$/)
+        return [loc.to_s]
+      end
     end
 
     # Find Collections with matching Docker image repository+tag pairs.
index fc445ae24edb5977a2092f7d92af86c300eb21ad..1ef0b797c044574a2b9eba72e1fcbd0aeba77e16 100644 (file)
@@ -191,7 +191,7 @@ class Job < ArvadosModel
       if self.cancelled_at and not self.cancelled_at_was
         self.cancelled_at = Time.now
         self.cancelled_by_user_uuid = current_user.uuid
-        self.cancelled_by_client_uuid = current_api_client.uuid
+        self.cancelled_by_client_uuid = current_api_client.andand.uuid
         @need_crunch_dispatch_trigger = true
       else
         self.cancelled_at = self.cancelled_at_was
index 6321145045fe2443206bcf67e2a8a035c11c2921..3058081c1e85b46f29060e35b138c205255d8545 100644 (file)
@@ -105,7 +105,7 @@ class Link < ArvadosModel
     end
   end
 
-  # A user can give all other users permissions on folders.
+  # A user can give all other users permissions on projects.
   def skip_uuid_read_permission_check
     skipped_attrs = super
     if link_class == "permission" and
index 39d7da94dc234ab4797d5cfe91a0d75a6378327e..d01c9dcbc00f9825e86356e88a4dacb527d33abf 100644 (file)
@@ -11,7 +11,7 @@
 #   hint-content ::= [A-Za-z0-9@_-]+
 #
 # Individual hints may have their own required format:
-# 
+#
 #   sign-hint      ::= "+A" <40 lowercase hex digits> "@" sign-timestamp
 #   sign-timestamp ::= <8 lowercase hex digits>
 
@@ -66,6 +66,15 @@ class Locator
     Locator.new(@hash, @size, @hints.reject { |o| o.start_with?("A") })
   end
 
+  def strip_hints
+    Locator.new(@hash, @size, [])
+  end
+
+  def strip_hints!
+    @hints = []
+    self
+  end
+
   def hash
     @hash
   end
index 65475853f2810e3a355c8f11085e5abec36d1571..64e0d09451992e8b43af7348a1b0d04c3cdcc212 100644 (file)
@@ -13,6 +13,8 @@ class User < ArvadosModel
   before_create :check_auto_admin
   after_create :add_system_group_permission_link
   after_create :send_admin_notifications
+  after_update :send_profile_created_notification
+
 
   has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid
 
@@ -161,7 +163,7 @@ class User < ArvadosModel
     # delete repo_perms for this user
     repo_perms = Link.where(tail_uuid: self.uuid,
                             link_class: 'permission',
-                            name: 'can_write')
+                            name: 'can_manage')
     repo_perms.each do |perm|
       Link.delete perm
     end
@@ -218,7 +220,7 @@ class User < ArvadosModel
 
   def check_auto_admin
     if User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0 and Rails.configuration.auto_admin_user
-      if current_user.email == Rails.configuration.auto_admin_user
+      if self.email == Rails.configuration.auto_admin_user
         self.is_admin = true
         self.is_active = true
       end
@@ -312,7 +314,7 @@ class User < ArvadosModel
       repo_perms = Link.where(tail_uuid: self.uuid,
                               head_uuid: repo[:uuid],
                               link_class: 'permission',
-                              name: 'can_write')
+                              name: 'can_manage')
       if repo_perms.any?
         logger.warn "User already has repository access " +
             repo_perms.collect { |p| p[:uuid] }.inspect
@@ -327,7 +329,7 @@ class User < ArvadosModel
     repo_perm = Link.create(tail_uuid: self.uuid,
                             head_uuid: repo[:uuid],
                             link_class: 'permission',
-                            name: 'can_write')
+                            name: 'can_manage')
     logger.info { "repo permission: " + repo_perm[:uuid] }
     return repo_perm
   end
@@ -429,4 +431,15 @@ class User < ArvadosModel
       AdminNotifier.new_inactive_user(self).deliver
     end
   end
+
+  # Send notification if the user saved profile for the first time
+  def send_profile_created_notification
+    if self.prefs_changed?
+      if self.prefs_was.andand.empty? || !self.prefs_was.andand['profile']
+        profile_notification_address = Rails.configuration.user_profile_notification_address
+        ProfileNotifier.profile_created(self, profile_notification_address).deliver if profile_notification_address
+      end
+    end
+  end
+
 end
diff --git a/services/api/app/views/profile_notifier/profile_created.text.erb b/services/api/app/views/profile_notifier/profile_created.text.erb
new file mode 100644 (file)
index 0000000..73adf28
--- /dev/null
@@ -0,0 +1,2 @@
+Profile created by user <%=@user.full_name%> <%=@user.email%>
+User's profile: <%=@user.prefs['profile']%>
index 827dfe158662e5c22109e8b7acc5ae922a5330b4..ddcaa57302b5ceec5437ab1b20cb09271b2f24ea 100644 (file)
@@ -40,10 +40,13 @@ test:
   active_support.deprecation: :stderr
   active_record.mass_assignment_sanitizer: :strict
   uuid_prefix: zzzzz
+  secret_token: <%= rand(2**512).to_s(36) %>
+  blob_signing_key: zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc
+
+  # email address to which mail should be sent when the user creates profile for the first time
+  user_profile_notification_address: arvados@example.com
 
 common:
-  secret_token: ~
-  blob_signing_key: ~
   uuid_prefix: <%= Digest::MD5.hexdigest(`hostname`).to_i(16).to_s(36)[0..4] %>
 
   # Git repositories must be readable by api server, or you won't be
@@ -94,6 +97,15 @@ common:
     - 192.168.1.1
   compute_node_ec2_tag_enable: false
 
+  # The version below is suitable for AWS.
+  # To use it, copy it to your application.yml, uncomment, and change <%# to <%=
+  # compute_node_nameservers: <%#
+  #   require 'net/http'
+  #   ['local', 'public'].collect do |iface|
+  #     Net::HTTP.get(URI("http://169.254.169.254/latest/meta-data/#{iface}-ipv4")).match(/^[\d\.]+$/)[0]
+  #   end << '172.16.0.23'
+  # %>
+
   accept_api_token: {}
 
   new_users_are_active: false
@@ -139,6 +151,12 @@ common:
   # address of the dedicated websocket server:
   #websocket_address: wss://127.0.0.1:3333/websocket
 
+  # blob_signing_key is a string of alphanumeric characters used to
+  # generate permission signatures for Keep locators. It must be
+  # identical to the permission key given to Keep. IMPORTANT: This is
+  # a site secret. It should be at least 50 characters.
+  blob_signing_key: ~
+
   # Amount of time (in seconds) for which a blob permission signature
   # remains valid.  Default: 2 weeks (1209600 seconds)
   blob_signing_ttl: 1209600
@@ -153,3 +171,11 @@ common:
   # do not turn this on if your users expect to keep data private from
   # one another!
   permit_create_collection_with_unsigned_manifest: false
+
+  # secret_token is a string of alphanumeric characters used by Rails
+  # to sign session tokens. IMPORTANT: This is a site secret. It
+  # should be at least 50 characters.
+  secret_token: ~
+
+  # email address to which mail should be sent when the user creates profile for the first time
+  user_profile_notification_address: false
index d6db3a4f8b7bcca3aea4c9d92974d9d131b307c5..d6de1ff9b55ac003bb4063332eaffef8f0ceb8f7 100644 (file)
 # 5. Section in application.default.yml called "common"
 
 development:
-  # The blob_signing_key is a string of alphanumeric characters used
-  # to sign permission hints for Keep locators. It must be identical
-  # to the permission key given to Keep.  If you run both apiserver
-  # and Keep in development, change this to a hardcoded string and
-  # make sure both systems use the same value.
+  # Mandatory site secrets. See application.default.yml for more info.
+  secret_token: ~
   blob_signing_key: ~
 
 production:
-  # At minimum, you need a nice long randomly generated secret_token here.
-  # Use a long string of alphanumeric characters (at least 36).
+  # Mandatory site secrets. See application.default.yml for more info.
   secret_token: ~
-
-  # blob_signing_key is required and must be identical to the
-  # permission secret provisioned to Keep.
-  # Use a long string of alphanumeric characters (at least 36).
   blob_signing_key: ~
-
   uuid_prefix: bogus
 
-  # compute_node_domain: example.org
-  # compute_node_nameservers:
-  #   - 127.0.0.1
-  #   - 192.168.1.1
-  #
-  # The version below is suitable for AWS.
-  # Uncomment and change <%# to <%= to use it.
-  # compute_node_nameservers: <%#
-  #   require 'net/http'
-  #   ['local', 'public'].collect do |iface|
-  #     Net::HTTP.get(URI("http://169.254.169.254/latest/meta-data/#{iface}-ipv4")).match(/^[\d\.]+$/)[0]
-  #   end << '172.16.0.23'
-  # %>
-
 test:
-  uuid_prefix: zzzzz
-  secret_token: <%= rand(2**512).to_s(36) %>
+  # Tests should be able to run without further configuration, but if you do
+  # want to change your local test configuration, this is where to do it.
 
 common:
-  #git_repositories_dir: /var/cache/git
-  #git_internal_dir: /var/cache/arvados/internal.git
-
+  # Settings in this section will be used in all environments
+  # (development, production, test) except when overridden in the
+  # environment-specific sections above.
index 70934553f24d2679b5d42393a14357a4daec85cd..74d2aea80a77027197638134647e696cc357d0c0 100644 (file)
@@ -15,6 +15,7 @@ Server::Application.routes.draw do
         get 'used_by', on: :member
       end
       resources :groups do
+        get 'contents', on: :collection
         get 'contents', on: :member
       end
       resources :humans
index deb6bf0374ca76d73427c1da3e03898eaf667e5d..15fd887adf68cb4994ccbab2bbe1a7760a524d6f 100644 (file)
@@ -2,9 +2,7 @@ class EmptyCollection < ActiveRecord::Migration
   include CurrentApiClient
 
   def up
-    act_as_system_user do
-      empty_collection
-    end
+    empty_collection
   end
 
   def down
index 61433a665fef774e0eaaebbe92ed7ffb09c816cb..aa05031fe0ec8c45a73d1fd41320f1c3913e10d5 100644 (file)
@@ -3,6 +3,7 @@
 --
 
 SET statement_timeout = 0;
+SET lock_timeout = 0;
 SET client_encoding = 'UTF8';
 SET standard_conforming_strings = on;
 SET check_function_bodies = false;
@@ -1967,6 +1968,8 @@ INSERT INTO schema_migrations (version) VALUES ('20140601022548');
 
 INSERT INTO schema_migrations (version) VALUES ('20140602143352');
 
+INSERT INTO schema_migrations (version) VALUES ('20140607150616');
+
 INSERT INTO schema_migrations (version) VALUES ('20140611173003');
 
 INSERT INTO schema_migrations (version) VALUES ('20140627210837');
index b4e6bbcf4d6f5c3b84c666bb6da615e92b5587b3..7bd475278ce7d320e643cd2e78e9636f54511995 100644 (file)
@@ -169,7 +169,7 @@ module CurrentApiClient
           $empty_collection = Collection.
             where(uuid: empty_collection_uuid).
             first_or_create!(manifest_text: '')
-          Link.where(tail_uuid: anonymous_group_uuid,
+          Link.where(tail_uuid: anonymous_group.uuid,
                      head_uuid: empty_collection_uuid,
                      link_class: 'permission',
                      name: 'can_read').first_or_create!
index 5a990f0cb41ad30bd8832399a0d40c1839f2460e..ddc0f3a89928643fbc4b448b837b4e122fd0fbc0 100755 (executable)
@@ -68,25 +68,28 @@ class Dispatcher
       begin
         sinfo.split("\n").
           each do |line|
-          re = line.match /(\S+?):+(idle|alloc|down)/
+          re = line.match /(\S+?):+(idle|alloc|down)?/
           next if !re
 
+          _, node_name, node_state = *re
+          node_state = 'down' unless %w(idle alloc down).include? node_state
+
           # sinfo tells us about a node N times if it is shared by N partitions
-          next if node_seen[re[1]]
-          node_seen[re[1]] = true
+          next if node_seen[node_name]
+          node_seen[node_name] = true
 
           # update our database (and cache) when a node's state changes
-          if @node_state[re[1]] != re[2]
-            @node_state[re[1]] = re[2]
-            node = Node.where('hostname=?', re[1]).order(:last_ping_at).last
+          if @node_state[node_name] != node_state
+            @node_state[node_name] = node_state
+            node = Node.where('hostname=?', node_name).order(:last_ping_at).last
             if node
-              $stderr.puts "dispatch: update #{re[1]} state to #{re[2]}"
-              node.info['slurm_state'] = re[2]
+              $stderr.puts "dispatch: update #{node_name} state to #{node_state}"
+              node.info['slurm_state'] = node_state
               if not node.save
                 $stderr.puts "dispatch: failed to update #{node.uuid}: #{node.errors.messages}"
               end
-            elsif re[2] != 'down'
-              $stderr.puts "dispatch: sinfo reports '#{re[1]}' is not down, but no node has that name"
+            elsif node_state != 'down'
+              $stderr.puts "dispatch: sinfo reports '#{node_name}' is not down, but no node has that name"
             end
           end
         end
@@ -375,11 +378,11 @@ class Dispatcher
       $stderr.puts j_done[:stderr_buf] + "\n"
     end
 
-    # Wait the thread
-    j_done[:wait_thr].value
+    # Wait the thread (returns a Process::Status)
+    exit_status = j_done[:wait_thr].value
 
     jobrecord = Job.find_by_uuid(job_done.uuid)
-    if jobrecord.started_at
+    if exit_status.to_i != 75 and jobrecord.started_at
       # Clean up state fields in case crunch-job exited without
       # putting the job in a suitable "finished" state.
       jobrecord.running = false
@@ -392,7 +395,18 @@ class Dispatcher
       # Don't fail the job if crunch-job didn't even get as far as
       # starting it. If the job failed to run due to an infrastructure
       # issue with crunch-job or slurm, we want the job to stay in the
-      # queue.
+      # queue. If crunch-job exited after losing a race to another
+      # crunch-job process, it exits 75 and we should leave the job
+      # record alone so the winner of the race do its thing.
+      #
+      # There is still an unhandled race condition: If our crunch-job
+      # process is about to lose a race with another crunch-job
+      # process, but crashes before getting to its "exit 75" (for
+      # example, "cannot fork" or "cannot reach API server") then we
+      # will assume incorrectly that it's our process's fault
+      # jobrecord.started_at is non-nil, and mark the job as failed
+      # even though the winner of the race is probably still doing
+      # fine.
     end
 
     # Invalidate the per-job auth token
index 4d465a67e5a93d9c704e0bb983f1d3073e9a37c2..c0bce93daa9e8fe83c784ad0d10a9365625e6953 100644 (file)
@@ -156,3 +156,21 @@ anonymous:
   api_token: 4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi
   expires_at: 2038-01-01 00:00:00
   scopes: ["GET /"]
+
+job_reader:
+  api_client: untrusted
+  user: job_reader
+  api_token: e99512cdc0f3415c2428b9758f33bdfb07bc3561b00e86e7e6
+  expires_at: 2038-01-01 00:00:00
+
+active_no_prefs:
+  api_client: untrusted
+  user: active_no_prefs
+  api_token: 3kg612cdc0f3415c2428b9758f33bdfb07bc3561b00e86qdmi
+  expires_at: 2038-01-01 00:00:00
+
+active_no_prefs_profile:
+  api_client: untrusted
+  user: active_no_prefs_profile
+  api_token: 3kg612cdc0f3415c242856758f33bdfb07bc3561b00e86qdmi
+  expires_at: 2038-01-01 00:00:00
index 1e9e158d1bad89e4cb3c61325154f8b72dec56cb..b0103fa2ab32074ca3a50c3676b539c917a9dcc9 100644 (file)
@@ -13,3 +13,19 @@ admin:
   key_type: SSH
   name: admin
   public_key: ssh-dss AAAAB3NzaC1kc3MAAACBAKy1IDMGwa7/Yjas77vLSShBE3SzpPXqXu6nRMC9zdIoMdctjhfP+GOOyQQP12rMs16NYmfdOxX+sa2t9syI/8NhDxTmNbHVw2jHimC6SL02v8WHDIw2vaBCVN+CHdeYbZsBB/8/M+2PO3uUWbr0TjoXcxrKYScS/aTTjSAWRg4ZAAAAFQDR/xAdrewj1ORNIQs+kWWdjmiO0wAAAIBC+G92r2ZeGaHLCMI0foKnfuQzg9fKp5krEvE6tvRNju7iOqtB9xe1qsAqr6GPZQjfSrNPac6T1pxMoh+an4PfNs5xgBIpvy93oqALd4maQt6483vsIyVCw6nQD7s/8IpIHpwxFEFs5/5moYxzY64eY0ldSXJwvPsrBTruhuUdugAAAIBut96rWQYTnYUdngyUK9EoJzgKn3l7gg0IQoFC4hS96D8vUm0wIdSEQHt01pSc0KR1Nnb4JrnNz/qCH45wOy5oB9msQ/2Pq2brTDZJcIPcN1LbMCps9PetUruz1OjK1NzDuLmvsrP3GBLxJrtmrCoKHLzPZ6QSefW0OymFgaDFGg==
+
+spectator:
+  uuid: zzzzz-fngyi-3uze1ipbnz2c2c2
+  owner_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
+  authorized_user_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
+  key_type: SSH
+  name: spectator
+  public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJK4hxmgXzg1gty+91JfkpgikAZxTvFTQoaFUJYTHIygz2V3FgU64NkK3yfwh+bhs7n8YIMftuCHfojKEJTtedbiv/mYpItetzdOwYONCGSEk1VnfipGhnFvL7FZDESTxLN9KNve3ZmZh8HvO6s8fdlTlqTTNKpsdwLiQn2s3W1TWvru/NP504MD5qPeZ4+8jZEh/uiuRaeXqPDAlE9QGPV4FRAA1xo0dBZIrRMwQC8kOttq/i2pLgHq1xW9p4J23oV68O/kkeBb7VwrX3Av/M61kvRsP8tA5gqh+HMKVO2qTP4yG6eGkAobIokQAcyZetPQIDmfVeoB0NzwPfAy4r
+
+project_viewer:
+  uuid: zzzzz-fngyi-5d3av1396niwcej
+  owner_uuid: zzzzz-tpzed-projectviewer1a
+  authorized_user_uuid: zzzzz-tpzed-projectviewer1a
+  key_type: SSH
+  name: project_viewer
+  public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPkOJMQzT9n6RousrLMU7c/KFKTI7I5JifDIEtGJJ1MMZW0GVoxtXALU90HcaRjEOwGPvQPxj7IDYqXs2N9uvm8SUWJMiz6c8NIjhGTkUoOnTFl4E9YTvkkKNs0P+3eT1Y+6zfTcFJHKP3AR4kZX+oiPHowRpCIlnLjXCFxX+E+YI554A7bS4yfOZO9lf6vtiT9I+6EqxC8a0hzZauPC1ZC3d/AFgBnrXJ2fBlAEySznru39quHN1u3v4qHTyaO2pDbG6vdI6O3JDCXCJKRv/B2FLuLTlzB0YesM1FiE6w8QgPxqb42B+uWTZb969UZliH8Pzw/mscOLAjmARDC02z
index 8a118039a8944821f29e5e298640af3de8ed87c2..fbd150cdf7887f7f164ea34521acec9d4f051b5e 100644 (file)
@@ -70,6 +70,18 @@ docker_image:
   updated_at: 2014-06-11T17:22:54Z
   manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
 
+unlinked_docker_image:
+  # This Collection contains a file that looks like a Docker image,
+  # but has no Docker metadata links pointing to it.
+  uuid: 9ae44d5792468c58bcf85ce7353c7027+124
+  owner_uuid: qr1hi-tpzed-000000000000000
+  created_at: 2014-06-11T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2014-06-11T17:22:54Z
+  updated_at: 2014-06-11T17:22:54Z
+  manifest_text: ". fca529cfe035e3e384563ee55eadbb2f+67108863 0:67108863:bcd02158b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
+
 empty:
   # Empty collection owned by anonymous_group is added with rake db:seed.
   uuid: d41d8cd98f00b204e9800998ecf8427e+0
index 62b9958010bc8b4480fb40a338907a5c2eb39675..c4ed1335ea4fb243d6e40cbccd3e1bbad204837f 100644 (file)
@@ -66,7 +66,7 @@ asubproject:
   updated_at: 2014-04-21 15:37:48 -0400
   name: A Subproject
   description: "Test project belonging to active user's first test project"
-  group_class: folder
+  group_class: project
 
 future_project_viewing_group:
   uuid: zzzzz-j7d0g-futrprojviewgrp
index e142faeb5bde344a8385f9b50b30f96a797049c6..3274381b60b020dfac475af00f30950d887d0163 100644 (file)
@@ -4,6 +4,7 @@ running:
   cancelled_at: ~
   cancelled_by_user_uuid: ~
   cancelled_by_client_uuid: ~
+  created_at: <%= 3.minute.ago.to_s(:db) %>
   started_at: <%= 3.minute.ago.to_s(:db) %>
   finished_at: ~
   script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
@@ -26,6 +27,7 @@ running_cancelled:
   cancelled_at: <%= 1.minute.ago.to_s(:db) %>
   cancelled_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   cancelled_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
+  created_at: <%= 4.minute.ago.to_s(:db) %>
   started_at: <%= 3.minute.ago.to_s(:db) %>
   finished_at: ~
   script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
@@ -49,6 +51,7 @@ uses_nonexistent_script_version:
   cancelled_by_user_uuid: ~
   cancelled_by_client_uuid: ~
   script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
+  created_at: <%= 5.minute.ago.to_s(:db) %>
   started_at: <%= 3.minute.ago.to_s(:db) %>
   finished_at: <%= 2.minute.ago.to_s(:db) %>
   running: false
@@ -73,6 +76,7 @@ foobar:
   script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
   script_parameters:
     input: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+  created_at: <%= 4.minute.ago.to_s(:db) %>
   started_at: <%= 3.minute.ago.to_s(:db) %>
   finished_at: <%= 2.minute.ago.to_s(:db) %>
   running: false
@@ -98,6 +102,7 @@ barbaz:
   script_parameters:
     input: fa7aeb5140e2848d39b416daeef4ffc5+45
     an_integer: 1
+  created_at: <%= 4.minute.ago.to_s(:db) %>
   started_at: <%= 3.minute.ago.to_s(:db) %>
   finished_at: <%= 2.minute.ago.to_s(:db) %>
   running: false
@@ -116,6 +121,7 @@ barbaz:
 
 previous_job_run:
   uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
+  created_at: <%= 14.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   repository: foo
   script: hash
@@ -128,6 +134,7 @@ previous_job_run:
 
 previous_docker_job_run:
   uuid: zzzzz-8i9sb-k6emstgk4kw4yhi
+  created_at: <%= 14.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   repository: foo
   script: hash
@@ -141,6 +148,7 @@ previous_docker_job_run:
 
 previous_job_run_no_output:
   uuid: zzzzz-8i9sb-cjs4pklxxjykppp
+  created_at: <%= 14.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   repository: foo
   script: hash
@@ -153,6 +161,7 @@ previous_job_run_no_output:
 
 nondeterminisic_job_run:
   uuid: zzzzz-8i9sb-cjs4pklxxjykyyy
+  created_at: <%= 14.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   repository: foo
   script: hash2
@@ -165,6 +174,7 @@ nondeterminisic_job_run:
 
 nearly_finished_job:
   uuid: zzzzz-8i9sb-2gx6rz0pjl033w3
+  created_at: <%= 14.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   repository: arvados
   script: doesnotexist
@@ -184,6 +194,7 @@ nearly_finished_job:
 
 queued:
   uuid: zzzzz-8i9sb-grx15v5mjnsyxk7
+  created_at: <%= 1.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   cancelled_at: ~
   cancelled_by_user_uuid: ~
index c221c7f750dd0c1ee6845445e93f04372b6fc845..4125757b9d63968bbb3bc0ed2ed4aa0a48051ef5 100644 (file)
@@ -250,7 +250,7 @@ foo_repository_readable_by_spectator:
   head_uuid: zzzzz-s0uqq-382brsig8rp3666
   properties: {}
 
-foo_repository_writable_by_active:
+foo_repository_manageable_by_active:
   uuid: zzzzz-o0j2j-8tdfjd8g0s4rn1k
   owner_uuid: zzzzz-tpzed-000000000000000
   created_at: 2014-01-24 20:42:26 -0800
@@ -260,7 +260,7 @@ foo_repository_writable_by_active:
   updated_at: 2014-01-24 20:42:26 -0800
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
-  name: can_write
+  name: can_manage
   head_uuid: zzzzz-s0uqq-382brsig8rp3666
   properties: {}
 
@@ -539,6 +539,20 @@ active_user_permission_to_docker_image_collection:
   head_uuid: fa3c1a9cb6783f85f2ecda037e07b8c3+167
   properties: {}
 
+active_user_permission_to_unlinked_docker_image_collection:
+  uuid: zzzzz-o0j2j-g5i0sa8cr3b1psf
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2014-01-24 20:42:26 -0800
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-000000000000000
+  modified_at: 2014-01-24 20:42:26 -0800
+  updated_at: 2014-01-24 20:42:26 -0800
+  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  link_class: permission
+  name: can_read
+  head_uuid: 9ae44d5792468c58bcf85ce7353c7027+124
+  properties: {}
+
 docker_image_collection_hash:
   uuid: zzzzz-o0j2j-dockercollhasha
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
@@ -633,3 +647,63 @@ anonymous_group_can_read_empty_collection:
   name: can_read
   tail_uuid: zzzzz-j7d0g-anonymouspublic
   head_uuid: d41d8cd98f00b204e9800998ecf8427e+0
+
+job_reader_can_read_previous_job_run:
+  # Permission link giving job_reader permission
+  # to read previous_job_run
+  uuid: zzzzz-o0j2j-8bbd851795ebafd
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2014-06-13 20:42:26 -0800
+  modified_by_client_uuid: zzzzz-tpzed-000000000000000
+  modified_by_user_uuid: zzzzz-tpzed-000000000000000
+  modified_at: 2014-06-13 20:42:26 -0800
+  updated_at: 2014-06-13 20:42:26 -0800
+  link_class: permission
+  name: can_read
+  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
+  head_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
+
+job_reader_can_read_foo_repo:
+  # Permission link giving job_reader permission
+  # to read foo_repo
+  uuid: zzzzz-o0j2j-072ec05dc9487f8
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2014-06-13 20:42:26 -0800
+  modified_by_client_uuid: zzzzz-tpzed-000000000000000
+  modified_by_user_uuid: zzzzz-tpzed-000000000000000
+  modified_at: 2014-06-13 20:42:26 -0800
+  updated_at: 2014-06-13 20:42:26 -0800
+  link_class: permission
+  name: can_read
+  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
+  head_uuid: zzzzz-s0uqq-382brsig8rp3666
+
+baz_collection_name_in_asubproject:
+  uuid: zzzzz-o0j2j-bazprojectname2
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-04-21 15:37:48 -0400
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  modified_at: 2014-04-21 15:37:48 -0400
+  updated_at: 2014-04-21 15:37:48 -0400
+  tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
+  head_uuid: ea10d51bcf88862dbcc36eb292017dfd+45
+  link_class: name
+  # This should resemble the default name assigned when a
+  # Collection is added to a Project.
+  name: "ea10d51bcf88862dbcc36eb292017dfd+45 added sometime"
+  properties: {}
+
+empty_collection_name_in_active_user_home_project:
+  uuid: zzzzz-o0j2j-i3n6m552x6tmoi4
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-08-06 22:11:51.242392533 Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  modified_at: 2014-08-06 22:11:51.242150425 Z
+  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  link_class: name
+  name: Empty collection
+  head_uuid: d41d8cd98f00b204e9800998ecf8427e+0
+  properties: {}
+  updated_at: 2014-08-06 22:11:51.242010312 Z
index f73558df401a3fd85fdced9cdb944c970dc26d08..7bfc2e1b3fe65d4882e2528eb6efaba07f858d6f 100644 (file)
@@ -2,11 +2,13 @@ new_pipeline:
   state: New
   uuid: zzzzz-d1hrv-f4gneyn6br1xize
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: <%= 1.minute.ago.to_s(:db) %>
 
 has_component_with_no_script_parameters:
   state: Ready
   uuid: zzzzz-d1hrv-1xfj6xkicf2muk2
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: <%= 10.minute.ago.to_s(:db) %>
   components:
    foo:
     script: foo
@@ -17,6 +19,7 @@ has_component_with_empty_script_parameters:
   state: Ready
   uuid: zzzzz-d1hrv-jq16l10gcsnyumo
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: <%= 3.minute.ago.to_s(:db) %>
   components:
    foo:
     script: foo
@@ -27,6 +30,7 @@ has_job:
   state: Ready
   uuid: zzzzz-d1hrv-1yfj6xkidf2muk3
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: <%= 3.1.minute.ago.to_s(:db) %>
   components:
    foo:
     script: foo
@@ -41,6 +45,7 @@ components_is_jobspec:
   # Helps test that clients cope with funny-shaped components.
   # For an example, see #3321.
   uuid: zzzzz-d1hrv-jobspeccomponts
+  created_at: <%= 30.minute.ago.to_s(:db) %>
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   created_at: 2014-04-14 12:35:04 -0400
   updated_at: 2014-04-14 12:35:04 -0400
index acb67b46f89c5aabb509d0027f4c552b6a04db00..5c688363181c2bb19ab740bb5cff6305e53260c8 100644 (file)
@@ -9,7 +9,10 @@ admin:
   identity_url: https://admin.openid.local
   is_active: true
   is_admin: true
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: IT
 
 miniadmin:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -20,7 +23,10 @@ miniadmin:
   identity_url: https://miniadmin.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: IT
 
 rominiadmin:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -31,7 +37,10 @@ rominiadmin:
   identity_url: https://rominiadmin.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: IT
 
 active:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -42,7 +51,10 @@ active:
   identity_url: https://active-user.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
 
 project_viewer:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -53,7 +65,10 @@ project_viewer:
   identity_url: https://project-viewer.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
 
 future_project_user:
   # Workbench tests give this user permission on aproject.
@@ -64,7 +79,10 @@ future_project_user:
   identity_url: https://future-project-user.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
 
 spectator:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -75,7 +93,10 @@ spectator:
   identity_url: https://spectator.openid.local
   is_active: true
   is_admin: false
-  prefs: {}
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
 
 inactive_uninvited:
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -119,3 +140,40 @@ anonymous:
   is_active: false
   is_admin: false
   prefs: {}
+
+job_reader:
+  owner_uuid: zzzzz-tpzed-000000000000000
+  uuid: zzzzz-tpzed-905b42d1dd4a354
+  email: jobber@arvados.local
+  first_name: Job
+  last_name: Er
+  identity_url: https://spectator.openid.local
+  is_active: true
+  is_admin: false
+  prefs:
+    profile:
+      organization: example.com
+      role: Computational biologist
+
+active_no_prefs:
+  owner_uuid: zzzzz-tpzed-000000000000000
+  uuid: zzzzz-tpzed-a46c42d1td4aoj4
+  email: active_no_prefs@arvados.local
+  first_name: NoPrefs
+  last_name: NoProfile
+  identity_url: https://active_no_prefs.openid.local
+  is_active: true
+  is_admin: false
+  prefs: {}
+
+active_no_prefs_profile:
+  owner_uuid: zzzzz-tpzed-000000000000000
+  uuid: zzzzz-tpzed-a46c98d1td4aoj4
+  email: active_no_prefs_profile@arvados.local
+  first_name: HasPrefs
+  last_name: NoProfile
+  identity_url: https://active_no_prefs_profile.openid.local
+  is_active: true
+  is_admin: false
+  prefs:
+    test: abc
index e4bbd5cd25d0506af16b21b79d02487627fc852f..9c7f4886affa7eeb559443e6364210ee80254700 100644 (file)
@@ -21,7 +21,52 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
     authorize_with :active
     get :index
     assert_response :success
-    assert_not_nil assigns(:objects)
+    assert(assigns(:objects).andand.any?, "no Collections returned in index")
+    refute(json_response["items"].any? { |c| c.has_key?("manifest_text") },
+           "basic Collections index included manifest_text")
+  end
+
+  test "can get non-database fields via index select" do
+    authorize_with :active
+    get(:index, filters: [["uuid", "=", collections(:foo_file).uuid]],
+        select: %w(uuid owner_uuid files))
+    assert_response :success
+    assert_equal(1, json_response["items"].andand.size,
+                 "wrong number of items returned for index")
+    assert_equal([[".", "foo", 3]], json_response["items"].first["files"],
+                 "wrong file list in index result")
+  end
+
+  test "can select only non-database fields for index" do
+    authorize_with :active
+    get(:index, select: %w(data_size files))
+    assert_response :success
+    assert(json_response["items"].andand.any?, "no items found in index")
+    json_response["items"].each do |coll|
+      assert_equal(coll["data_size"],
+                   coll["files"].inject(0) { |size, fspec| size + fspec.last },
+                   "mismatch between data size and file list")
+    end
+  end
+
+  test "index with manifest_text selected returns signed locators" do
+    columns = %w(uuid owner_uuid data_size files manifest_text)
+    authorize_with :active
+    get :index, select: columns
+    assert_response :success
+    assert(assigns(:objects).andand.any?,
+           "no Collections returned for index with columns selected")
+    json_response["items"].each do |coll|
+      assert_equal(columns, columns & coll.keys,
+                   "Collections index did not respect selected columns")
+      loc_regexp = / [[:xdigit:]]{32}\+\d+\S+/
+      pos = 0
+      while match = loc_regexp.match(coll["manifest_text"], pos)
+        assert_match(/\+A[[:xdigit:]]+@[[:xdigit:]]{8}\b/, match.to_s,
+                     "Locator in manifest_text was not signed")
+        pos = match.end(0)
+      end
+    end
   end
 
   [0,1,2].each do |limit|
index 0b7602942af27cdf087079ca5b319b450694121c..217809ae6569cf55b066cd776fd187405b5519b4 100644 (file)
@@ -16,11 +16,11 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   test "get list of projects" do
     authorize_with :active
-    get :index, filters: [['group_class', 'in', ['project', 'folder']]], format: :json
+    get :index, filters: [['group_class', '=', 'project']], format: :json
     assert_response :success
     group_uuids = []
     json_response['items'].each do |group|
-      assert_includes ['folder', 'project'], group['group_class']
+      assert_equal 'project', group['group_class']
       group_uuids << group['uuid']
     end
     assert_includes group_uuids, groups(:aproject).uuid
@@ -61,6 +61,13 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
     expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job'
     assert_equal expect_kinds, (expect_kinds & kinds)
+
+    json_response['items'].each do |i|
+      if i['kind'] == 'arvados#group'
+        assert(i['group_class'] == 'project',
+               "group#contents returned a non-project group")
+      end
+    end
   end
 
   test 'get group-owned objects' do
@@ -83,6 +90,77 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     check_project_contents_response
   end
 
+  [false, true].each do |include_linked|
+    test "list objects across projects, include_linked=#{include_linked}" do
+      authorize_with :project_viewer
+      get :contents, {
+        format: :json,
+        include_linked: include_linked,
+        filters: [['uuid', 'is_a', 'arvados#specimen']]
+      }
+      assert_response :success
+      found_uuids = json_response['items'].collect { |i| i['uuid'] }
+      [[:in_aproject, true],
+       [:in_asubproject, true],
+       [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+        if should_find
+          assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+        else
+          refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+        end
+      end
+    end
+  end
+
+  [false, true].each do |include_linked|
+    test "list objects in home project, include_linked=#{include_linked}" do
+      authorize_with :active
+      get :contents, {
+        format: :json,
+        id: users(:active).uuid,
+        include_linked: include_linked,
+      }
+      assert_response :success
+      found_uuids = json_response['items'].collect { |i| i['uuid'] }
+      if include_linked
+        assert_includes found_uuids, collections(:empty).uuid, "empty collection did not appear in home project"
+      end
+      assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
+      refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
+    end
+  end
+
+  test "user with project read permission can see project collections" do
+    authorize_with :project_viewer
+    get :contents, {
+      id: groups(:asubproject).uuid,
+      format: :json,
+      include_linked: true,
+    }
+    ids = json_response['items'].map { |item| item["uuid"] }
+    assert_includes ids, collections(:baz_file).uuid
+  end
+
+  test 'list objects across multiple projects' do
+    authorize_with :project_viewer
+    get :contents, {
+      format: :json,
+      include_linked: false,
+      filters: [['uuid', 'is_a', 'arvados#specimen']]
+    }
+    assert_response :success
+    found_uuids = json_response['items'].collect { |i| i['uuid'] }
+    [[:in_aproject, true],
+     [:in_asubproject, true],
+     [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+      if should_find
+        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+      else
+        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+      end
+    end
+  end
+
   # Even though the project_viewer tests go through other controllers,
   # I'm putting them here so they're easy to find alongside the other
   # project tests.
index f17923d4a987ba661d97781238ef5e2c53c5e3ef..01f2b9c51b50f2d2f674566f1c962518d191c18b 100644 (file)
@@ -110,6 +110,27 @@ class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
     assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
   end
 
+  test "do not reuse job because output is not readable by user" do
+    authorize_with :job_reader
+    post :create, {
+      job: {
+        script: "hash",
+        script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
+        repository: "foo",
+        script_parameters: {
+          input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
+          an_integer: '1'
+        }
+      },
+      find_or_create: true
+    }
+    assert_response :success
+    assert_not_nil assigns(:object)
+    new_job = JSON.parse(@response.body)
+    assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
+    assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
+  end
+
   test "test_cannot_reuse_job_no_output" do
     post :create, job: {
       no_reuse: false,
index 4b1381edb8f553667ab8aaa4139104b163b24839..0793d12e4e5103c0298f6b96122a9558d489677d 100644 (file)
@@ -42,6 +42,38 @@ class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
     end
   end
 
+  test "get_all_permissions does not give any access to user without permission" do
+    authorize_with :admin
+    get :get_all_permissions
+    assert_response :success
+    assert_equal(authorized_keys(:project_viewer).authorized_user_uuid,
+                 users(:project_viewer).uuid,
+                 "project_viewer must have an authorized_key for this test to work")
+    json_response['repositories'].each do |repo|
+      assert_equal(false,
+                   repo['user_permissions'].has_key?(users(:project_viewer).uuid),
+                   "project_viewer user should not have perms for #{repo['uuid']}")
+    end
+  end
+
+  test "get_all_permissions gives gitolite R to user with read-only access" do
+    authorize_with :admin
+    get :get_all_permissions
+    assert_response :success
+    found_it = false
+    assert_equal(authorized_keys(:spectator).authorized_user_uuid,
+                 users(:spectator).uuid,
+                 "spectator must have an authorized_key for this test to work")
+    json_response['repositories'].each do |repo|
+      next unless repo['uuid'] == repositories(:foo).uuid
+      assert_equal('R',
+                   repo['user_permissions'][users(:spectator).uuid]['gitolite_permissions'],
+                   "spectator user should have just R access to #{repo['uuid']}")
+      found_it = true
+    end
+    assert_equal true, found_it, "spectator user does not have R on foo repo"
+  end
+
   test "get_all_permissions provides admin and active user keys" do
     authorize_with :admin
     get :get_all_permissions
index cdbc79bf8134bc34f2437259fa090fe82b874ebd..a448d1a4bdcf3d90df109c863474a105cf48ea9a 100644 (file)
@@ -121,7 +121,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
         created['uuid'], created['email'], 'arvados#user', false, 'User'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         repo_name, created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
@@ -158,7 +158,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     # arvados#user, repo link and link add user to 'All users' group
     verify_num_links @all_links_at_start, 5
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         repo_name, created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
@@ -256,7 +256,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'expecting inactive user email'
 
     # expect repo and vm links
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'test_repo', resp_obj['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
@@ -479,7 +479,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
         created['uuid'], created['email'], 'arvados#user', false, 'User'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'test_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
@@ -539,7 +539,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
         created['uuid'], created['email'], 'arvados#user', false, 'User'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'test_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
@@ -605,7 +605,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', false, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', false, 'permission', 'can_manage',
         'test_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
@@ -629,7 +629,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'new_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
@@ -657,7 +657,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     # since no repo name in input, we won't get any; even though user has one
-    verify_link response_items, 'arvados#repository', false, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', false, 'permission', 'can_manage',
         'new_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
@@ -689,7 +689,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_write',
+    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
         'test_repo', created['uuid'], 'arvados#repository', true, 'Repository'
 
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
@@ -842,6 +842,71 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     check_active_users_index
   end
 
+  test "update active_no_prefs user profile and expect notification email" do
+    authorize_with :admin
+
+    put :update, {
+      id: users(:active_no_prefs).uuid,
+      user: {
+        prefs: {:profile => {'organization' => 'example.com'}}
+      }
+    }
+    assert_response :success
+
+    found_email = false
+    ActionMailer::Base.deliveries.andand.each do |email|
+      if email.subject == "Profile created by #{users(:active_no_prefs).email}"
+        found_email = true
+        break
+      end
+    end
+    assert_equal true, found_email, 'Expected email after creating profile'
+  end
+
+  test "update active_no_prefs_profile user profile and expect notification email" do
+    authorize_with :admin
+
+    user = {}
+    user[:prefs] = users(:active_no_prefs_profile).prefs
+    user[:prefs][:profile] = {:profile => {'organization' => 'example.com'}}
+    put :update, {
+      id: users(:active_no_prefs_profile).uuid,
+      user: user
+    }
+    assert_response :success
+
+    found_email = false
+    ActionMailer::Base.deliveries.andand.each do |email|
+      if email.subject == "Profile created by #{users(:active_no_prefs_profile).email}"
+        found_email = true
+        break
+      end
+    end
+    assert_equal true, found_email, 'Expected email after creating profile'
+  end
+
+  test "update active user profile and expect no notification email" do
+    authorize_with :admin
+
+    put :update, {
+      id: users(:active).uuid,
+      user: {
+        prefs: {:profile => {'organization' => 'anotherexample.com'}}
+      }
+    }
+    assert_response :success
+
+    found_email = false
+    ActionMailer::Base.deliveries.andand.each do |email|
+      if email.subject == "Profile created by #{users(:active).email}"
+        found_email = true
+        break
+      end
+    end
+    assert_equal false, found_email, 'Expected no email after updating profile'
+  end
+
+
   NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
                          "last_name"].sort
 
@@ -963,8 +1028,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end
 
     repo_perms = Link.where(tail_uuid: uuid,
-                              link_class: 'permission',
-                              name: 'can_write').where("head_uuid like ?", Repository.uuid_like_pattern)
+                            link_class: 'permission',
+                            name: 'can_manage').where("head_uuid like ?", Repository.uuid_like_pattern)
     if expect_repo_perms
       assert repo_perms.any?, "expected repo_perms"
     else
index e1ca7c53076e0bde35986ea0b5e07a1443e99e36..73a676f2fd3276143d5f1b2c007c5d84555c304a 100644 (file)
@@ -103,6 +103,13 @@ class JobTest < ActiveSupport::TestCase
     assert(job.invalid?, "non-Docker Collection constraint was valid")
   end
 
+  test "can create Job with Docker image Collection without Docker links" do
+    image_uuid = collections(:unlinked_docker_image).uuid
+    job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
+    assert(job.valid?, "Job created with unlinked Docker image was invalid")
+    assert_equal(image_uuid, job.docker_image_locator)
+  end
+
   test "can't create Job with Docker image locator" do
     begin
       job = Job.new job_attrs(docker_image_locator: BAD_COLLECTION)
index d4f25245c31e182a2895a4b4b6480cbaee4f7220..fb871724e5eedb995e5ad56a5ed9504786a32784 100644 (file)
@@ -204,7 +204,7 @@ class UserTest < ActiveSupport::TestCase
     verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
 
     repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_write', resp_user[:uuid], nil
+    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
 
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
@@ -246,7 +246,7 @@ class UserTest < ActiveSupport::TestCase
     verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
 
     repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_write', resp_user[:uuid], nil
+    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
 
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
@@ -286,7 +286,7 @@ class UserTest < ActiveSupport::TestCase
     verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
 
     repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_write', resp_user[:uuid], nil
+    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
 
     # invoke setup again with a vm_uuid
     vm = VirtualMachine.create
@@ -301,7 +301,7 @@ class UserTest < ActiveSupport::TestCase
     verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
 
     repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_write', resp_user[:uuid], nil
+    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
 
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
diff --git a/services/crunch/crunchstat/go.sh b/services/crunch/crunchstat/go.sh
deleted file mode 100755 (executable)
index 640a0d2..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#! /bin/sh
-
-# Wraps the 'go' executable with some environment setup.  Sets GOPATH, creates
-# 'pkg' and 'bin' directories, automatically installs dependencies, then runs
-# the underlying 'go' executable with any command line parameters provided to
-# the script.
-
-rootdir=$(readlink -f $(dirname $0))
-GOPATH=$rootdir:$rootdir/../../sdk/go:$GOPATH
-export GOPATH
-
-mkdir -p $rootdir/pkg
-mkdir -p $rootdir/bin
-
-go $*
index 5d782d2641133643546622b95713d88b8587b15b..ec5e94af57b312ead31f2ffaa1e5ff3c09a15f55 100644 (file)
@@ -17,6 +17,8 @@ import apiclient
 import json
 import logging
 
+_logger = logging.getLogger('arvados.arvados_fuse')
+
 from time import time
 from llfuse import FUSEError
 
@@ -124,7 +126,7 @@ class Directory(FreshBase):
             try:
                 self.update()
             except apiclient.errors.HttpError as e:
-                logging.debug(e)
+                _logger.debug(e)
 
     def __getitem__(self, item):
         self.checkupdate()
@@ -197,7 +199,8 @@ class CollectionDirectory(Directory):
             self.fresh()
             return True
         except Exception as detail:
-            logging.debug("arv-mount %s: error: %s" % (self.collection_locator,detail))
+            _logger.debug("arv-mount %s: error: %s",
+                          self.collection_locator, detail)
             return False
 
 class MagicDirectory(Directory):
@@ -225,7 +228,7 @@ class MagicDirectory(Directory):
             else:
                 return False
         except Exception as e:
-            logging.debug('arv-mount exception keep %s', e)
+            _logger.debug('arv-mount exception keep %s', e)
             return False
 
     def __getitem__(self, item):
@@ -305,7 +308,8 @@ class GroupsDirectory(Directory):
                 self._entries[a].invalidate()
 
     def update(self):
-        groups = self.api.groups().list().execute()
+        groups = self.api.groups().list(
+            filters=[['group_class','=','project']]).execute()
         self.merge(groups['items'],
                    lambda i: i['uuid'],
                    lambda a, i: a.uuid == i['uuid'],
@@ -475,7 +479,8 @@ class Operations(llfuse.Operations):
         return entry
 
     def lookup(self, parent_inode, name):
-        logging.debug("arv-mount lookup: parent_inode %i name %s", parent_inode, name)
+        _logger.debug("arv-mount lookup: parent_inode %i name %s",
+                      parent_inode, name)
         inode = None
 
         if name == '.':
@@ -511,7 +516,7 @@ class Operations(llfuse.Operations):
         return fh
 
     def read(self, fh, off, size):
-        logging.debug("arv-mount read %i %i %i", fh, off, size)
+        _logger.debug("arv-mount read %i %i %i", fh, off, size)
         if fh in self._filehandles:
             handle = self._filehandles[fh]
         else:
@@ -528,7 +533,7 @@ class Operations(llfuse.Operations):
             del self._filehandles[fh]
 
     def opendir(self, inode):
-        logging.debug("arv-mount opendir: inode %i", inode)
+        _logger.debug("arv-mount opendir: inode %i", inode)
 
         if inode in self.inodes:
             p = self.inodes[inode]
@@ -549,14 +554,14 @@ class Operations(llfuse.Operations):
         return fh
 
     def readdir(self, fh, off):
-        logging.debug("arv-mount readdir: fh %i off %i", fh, off)
+        _logger.debug("arv-mount readdir: fh %i off %i", fh, off)
 
         if fh in self._filehandles:
             handle = self._filehandles[fh]
         else:
             raise llfuse.FUSEError(errno.EBADF)
 
-        logging.debug("arv-mount handle.entry %s", handle.entry)
+        _logger.debug("arv-mount handle.entry %s", handle.entry)
 
         e = off
         while e < len(handle.entry):
index 0f3e8719b9f683a6bedbed470b280b0b65d2f710..794a468fbc6c064a1685e162caa761c476c5b519 100755 (executable)
@@ -7,10 +7,11 @@ import logging
 import os
 import signal
 import subprocess
-import traceback
 
 from arvados_fuse import *
 
+logger = logging.getLogger('arvados.arv-mount')
+
 if __name__ == '__main__':
     # Handle command line parameters
     parser = argparse.ArgumentParser(
@@ -52,17 +53,30 @@ collections on the server.""")
     else:
         daemon_ctx = None
 
-    # Set up logging.
-    # If we're daemonized without a logfile, there's nowhere to log, so don't.
-    if args.logfile or (daemon_ctx is None):
-        log_conf = {}
-        if args.debug:
-            log_conf['level'] = logging.DEBUG
-            arvados.config.settings()['ARVADOS_DEBUG'] = 'true'
-        if args.logfile:
-            log_conf['filename'] = args.logfile
-        logging.basicConfig(**log_conf)
-        logging.debug("arv-mount debugging enabled")
+    # Configure a logger based on command-line switches.
+    # If we're using a contemporary Python SDK (mid-August 2014),
+    # configure the arvados hierarchy logger.
+    # Otherwise, configure the program root logger.
+    base_logger = getattr(arvados, 'logger', None)
+
+    if args.logfile:
+        log_handler = logging.FileHandler(args.logfile)
+    elif daemon_ctx:
+        log_handler = logging.NullHandler()
+    elif base_logger:
+        log_handler = arvados.log_handler
+    else:
+        log_handler = logging.StreamHandler()
+
+    if base_logger is None:
+        base_logger = logging.getLogger()
+    else:
+        base_logger.removeHandler(arvados.log_handler)
+    base_logger.addHandler(log_handler)
+
+    if args.debug:
+        base_logger.setLevel(logging.DEBUG)
+        logger.debug("arv-mount debugging enabled")
 
     try:
         # Create the request handler
@@ -79,9 +93,8 @@ collections on the server.""")
         else:
             # Set up the request handler with the 'magic directory' at the root
             operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes))
-    except Exception as ex:
-        logging.error("arv-mount: exception during API setup")
-        logging.error(traceback.format_exc())
+    except Exception:
+        logger.exception("arv-mount: exception during API setup")
         exit(1)
 
     # FUSE options, see mount.fuse(8)
@@ -115,8 +128,8 @@ collections on the server.""")
             signal.signal(signal.SIGTERM, signal.SIG_DFL)
             signal.signal(signal.SIGQUIT, signal.SIG_DFL)
         except Exception as e:
-            logging.error('arv-mount: exception during exec %s' % (args.exec_args,))
-            logging.error(traceback.format_exc())
+            logger.exception('arv-mount: exception during exec %s',
+                             args.exec_args)
             try:
                 rc = e.errno
             except AttributeError:
@@ -130,6 +143,5 @@ collections on the server.""")
             llfuse.init(operations, args.mountpoint, opts)
             llfuse.main()
         except Exception as e:
-            logging.error('arv-mount: exception during mount')
-            logging.error(traceback.format_exc())
+            logger.exception('arv-mount: exception during mount')
             exit(getattr(e, 'errno', 1))
index 62e513a1b9566145b2d03d9e16014f63000bc69a..fa64a5c4a485642437c6c0d7b1d719168e098cfb 100644 (file)
@@ -325,6 +325,7 @@ class FuseGroupsTest(MountTestBase):
         d3 = os.listdir(os.path.join(self.mounttmp, 'zzzzz-j7d0g-v955i6s2oi1cbso', 'zzzzz-j7d0g-axqo7eu9pwvna1x'))
         d3.sort()
         self.assertEqual(["I'm in a subproject, too",
+                          "ea10d51bcf88862dbcc36eb292017dfd+45 added sometime",
                           "zzzzz-j58dm-c40lddwcqqr1ffs"
                       ], d3)
 
diff --git a/services/keep/go.sh b/services/keep/go.sh
deleted file mode 100755 (executable)
index 156fe90..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#! /bin/sh
-
-# Wraps the 'go' executable with some environment setup.  Sets GOPATH, creates
-# 'pkg' and 'bin' directories, automatically installs dependencies, then runs
-# the underlying 'go' executable with any command line parameters provided to
-# the script.
-
-rootdir=$(readlink -f $(dirname $0))
-GOPATH=$rootdir:$rootdir/../../sdk/go:$GOPATH
-export GOPATH
-
-mkdir -p $rootdir/pkg
-mkdir -p $rootdir/bin
-
-go get github.com/gorilla/mux
-
-go $*
similarity index 98%
rename from services/keep/src/arvados.org/keepproxy/keepproxy.go
rename to services/keepproxy/keepproxy.go
index 367854bed382ec1b17589c825126d5e31a9bf6a6..a927b87ea6575812c1b5fb728e50e946b6b46589 100644 (file)
@@ -1,8 +1,8 @@
 package main
 
 import (
-       "arvados.org/keepclient"
-       "arvados.org/sdk"
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "flag"
        "fmt"
        "github.com/gorilla/mux"
@@ -68,7 +68,7 @@ func main() {
 
        flagset.Parse(os.Args[1:])
 
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        if err != nil {
                log.Fatalf("Error setting up arvados client %s", err.Error())
        }
similarity index 85%
rename from services/keep/src/arvados.org/keepproxy/keepproxy_test.go
rename to services/keepproxy/keepproxy_test.go
index 4bf347890eb01edea36af772fed2bbef9f4f91d1..c4e5ae7258f0963093232f78877820ed15c0296c 100644 (file)
@@ -1,8 +1,8 @@
 package main
 
 import (
-       "arvados.org/keepclient"
-       "arvados.org/sdk"
+       "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "crypto/md5"
        "crypto/tls"
        "fmt"
@@ -14,7 +14,6 @@ import (
        "net/url"
        "os"
        "os/exec"
-       "strings"
        "testing"
        "time"
 )
@@ -31,8 +30,8 @@ var _ = Suite(&ServerRequiredSuite{})
 type ServerRequiredSuite struct{}
 
 func pythonDir() string {
-       gopath := os.Getenv("GOPATH")
-       return fmt.Sprintf("%s/../../sdk/python/tests", strings.Split(gopath, ":")[0])
+       cwd, _ := os.Getwd()
+       return fmt.Sprintf("%s/../../sdk/python/tests", cwd)
 }
 
 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
@@ -40,12 +39,27 @@ func (s *ServerRequiredSuite) SetUpSuite(c *C) {
        defer os.Chdir(cwd)
 
        os.Chdir(pythonDir())
-
-       if err := exec.Command("python", "run_test_server.py", "start").Run(); err != nil {
-               panic("'python run_test_server.py start' returned error")
+       {
+               cmd := exec.Command("python", "run_test_server.py", "start")
+               stderr, err := cmd.StderrPipe()
+               if err != nil {
+                       log.Fatalf("Setting up stderr pipe: %s", err)
+               }
+               go io.Copy(os.Stderr, stderr)
+               if err := cmd.Run(); err != nil {
+                       panic(fmt.Sprintf("'python run_test_server.py start' returned error %s", err))
+               }
        }
-       if err := exec.Command("python", "run_test_server.py", "start_keep").Run(); err != nil {
-               panic("'python run_test_server.py start_keep' returned error")
+       {
+               cmd := exec.Command("python", "run_test_server.py", "start_keep")
+               stderr, err := cmd.StderrPipe()
+               if err != nil {
+                       log.Fatalf("Setting up stderr pipe: %s", err)
+               }
+               go io.Copy(os.Stderr, stderr)
+               if err := cmd.Run(); err != nil {
+                       panic(fmt.Sprintf("'python run_test_server.py start_keep' returned error %s", err))
+               }
        }
 
        os.Setenv("ARVADOS_API_HOST", "localhost:3001")
@@ -109,7 +123,7 @@ func runProxy(c *C, args []string, token string, port int) keepclient.KeepClient
 
        os.Setenv("ARVADOS_KEEP_PROXY", fmt.Sprintf("http://localhost:%v", port))
        os.Setenv("ARVADOS_API_TOKEN", token)
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, err := keepclient.MakeKeepClient(&arv)
        c.Check(kc.Using_proxy, Equals, true)
        c.Check(len(kc.ServiceRoots()), Equals, 1)
@@ -131,7 +145,7 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
        setupProxyService()
 
        os.Setenv("ARVADOS_EXTERNAL_CLIENT", "true")
-       arv, err := sdk.MakeArvadosClient()
+       arv, err := arvadosclient.MakeArvadosClient()
        kc, err := keepclient.MakeKeepClient(&arv)
        c.Check(kc.Arvados.External, Equals, true)
        c.Check(kc.Using_proxy, Equals, true)
@@ -156,7 +170,7 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
                var rep int
                var err error
                hash2, rep, err = kc.PutB([]byte("foo"))
-               c.Check(hash2, Equals, fmt.Sprintf("%s+3", hash))
+               c.Check(hash2, Matches, fmt.Sprintf(`^%s\+3(\+.+)?$`, hash))
                c.Check(rep, Equals, 2)
                c.Check(err, Equals, nil)
                log.Print("PutB")
@@ -238,7 +252,7 @@ func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
 
        {
                hash2, rep, err := kc.PutB([]byte("baz"))
-               c.Check(hash2, Equals, fmt.Sprintf("%s+3", hash))
+               c.Check(hash2, Matches, fmt.Sprintf(`^%s\+3(\+.+)?$`, hash))
                c.Check(rep, Equals, 2)
                c.Check(err, Equals, nil)
                log.Print("PutB")
similarity index 76%
rename from services/keep/src/keep/handler_test.go
rename to services/keepstore/handler_test.go
index 1b0a90fb4e7096c38d2236629122bd82c8ad3da5..64a417f9a7ae24d01d4905c7df34437fbd4c8fc6 100644 (file)
@@ -11,10 +11,12 @@ package main
 
 import (
        "bytes"
+       "encoding/json"
        "fmt"
        "github.com/gorilla/mux"
        "net/http"
        "net/http/httptest"
+       "os"
        "regexp"
        "strings"
        "testing"
@@ -42,7 +44,7 @@ func TestGetHandler(t *testing.T) {
 
        // Prepare two test Keep volumes. Our block is stored on the second volume.
        KeepVM = MakeTestVolumeManager(2)
-       defer func() { KeepVM.Quit() }()
+       defer KeepVM.Quit()
 
        vols := KeepVM.Volumes()
        if err := vols[0].Put(TEST_HASH, TEST_BLOCK); err != nil {
@@ -59,11 +61,11 @@ func TestGetHandler(t *testing.T) {
        permission_ttl = time.Duration(300) * time.Second
 
        var (
-               unsigned_locator  = "http://localhost:25107/" + TEST_HASH
+               unsigned_locator  = "/" + TEST_HASH
                valid_timestamp   = time.Now().Add(permission_ttl)
                expired_timestamp = time.Now().Add(-time.Hour)
-               signed_locator    = "http://localhost:25107/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
-               expired_locator   = "http://localhost:25107/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
+               signed_locator    = "/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
+               expired_locator   = "/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
        )
 
        // -----------------
@@ -151,7 +153,7 @@ func TestPutHandler(t *testing.T) {
 
        // Prepare two test Keep volumes.
        KeepVM = MakeTestVolumeManager(2)
-       defer func() { KeepVM.Quit() }()
+       defer KeepVM.Quit()
 
        // Set up a REST router for testing the handlers.
        rest := MakeRESTRouter()
@@ -161,7 +163,7 @@ func TestPutHandler(t *testing.T) {
 
        // Unauthenticated request, no server key
        // => OK (unsigned response)
-       unsigned_locator := "http://localhost:25107/" + TEST_HASH
+       unsigned_locator := "/" + TEST_HASH
        response := IssueRequest(rest,
                &RequestTester{
                        method:       "PUT",
@@ -245,7 +247,7 @@ func TestIndexHandler(t *testing.T) {
        // Include multiple blocks on different volumes, and
        // some metadata files (which should be omitted from index listings)
        KeepVM = MakeTestVolumeManager(2)
-       defer func() { KeepVM.Quit() }()
+       defer KeepVM.Quit()
 
        vols := KeepVM.Volumes()
        vols[0].Put(TEST_HASH, TEST_BLOCK)
@@ -260,30 +262,30 @@ func TestIndexHandler(t *testing.T) {
 
        unauthenticated_req := &RequestTester{
                method: "GET",
-               uri:    "http://localhost:25107/index",
+               uri:    "/index",
        }
        authenticated_req := &RequestTester{
                method:    "GET",
-               uri:       "http://localhost:25107/index",
+               uri:       "/index",
                api_token: known_token,
        }
        superuser_req := &RequestTester{
                method:    "GET",
-               uri:       "http://localhost:25107/index",
+               uri:       "/index",
                api_token: data_manager_token,
        }
        unauth_prefix_req := &RequestTester{
                method: "GET",
-               uri:    "http://localhost:25107/index/" + TEST_HASH[0:3],
+               uri:    "/index/" + TEST_HASH[0:3],
        }
        auth_prefix_req := &RequestTester{
                method:    "GET",
-               uri:       "http://localhost:25107/index/" + TEST_HASH[0:3],
+               uri:       "/index/" + TEST_HASH[0:3],
                api_token: known_token,
        }
        superuser_prefix_req := &RequestTester{
                method:    "GET",
-               uri:       "http://localhost:25107/index/" + TEST_HASH[0:3],
+               uri:       "/index/" + TEST_HASH[0:3],
                api_token: data_manager_token,
        }
 
@@ -411,6 +413,131 @@ func TestIndexHandler(t *testing.T) {
        }
 }
 
+// TestDeleteHandler
+//
+// Cases tested:
+//
+//   With no token and with a non-data-manager token:
+//   * Delete existing block
+//     (test for 403 Forbidden, confirm block not deleted)
+//
+//   With data manager token:
+//
+//   * Delete existing block
+//     (test for 200 OK, response counts, confirm block deleted)
+//
+//   * Delete nonexistent block
+//     (test for 200 OK, response counts)
+//
+//   TODO(twp):
+//
+//   * Delete block on read-only and read-write volume
+//     (test for 200 OK, response with copies_deleted=1,
+//     copies_failed=1, confirm block deleted only on r/w volume)
+//
+//   * Delete block on read-only volume only
+//     (test for 200 OK, response with copies_deleted=0, copies_failed=1,
+//     confirm block not deleted)
+//
+func TestDeleteHandler(t *testing.T) {
+       defer teardown()
+
+       // Set up Keep volumes and populate them.
+       // Include multiple blocks on different volumes, and
+       // some metadata files (which should be omitted from index listings)
+       KeepVM = MakeTestVolumeManager(2)
+       defer KeepVM.Quit()
+
+       vols := KeepVM.Volumes()
+       vols[0].Put(TEST_HASH, TEST_BLOCK)
+
+       // Set up a REST router for testing the handlers.
+       rest := MakeRESTRouter()
+
+       var user_token = "NOT DATA MANAGER TOKEN"
+       data_manager_token = "DATA MANAGER TOKEN"
+
+       unauth_req := &RequestTester{
+               method: "DELETE",
+               uri:    "/" + TEST_HASH,
+       }
+
+       user_req := &RequestTester{
+               method:    "DELETE",
+               uri:       "/" + TEST_HASH,
+               api_token: user_token,
+       }
+
+       superuser_existing_block_req := &RequestTester{
+               method:    "DELETE",
+               uri:       "/" + TEST_HASH,
+               api_token: data_manager_token,
+       }
+
+       superuser_nonexistent_block_req := &RequestTester{
+               method:    "DELETE",
+               uri:       "/" + TEST_HASH_2,
+               api_token: data_manager_token,
+       }
+
+       // Unauthenticated request returns PermissionError.
+       var response *httptest.ResponseRecorder
+       response = IssueRequest(rest, unauth_req)
+       ExpectStatusCode(t,
+               "unauthenticated request",
+               PermissionError.HTTPCode,
+               response)
+
+       // Authenticated non-admin request returns PermissionError.
+       response = IssueRequest(rest, user_req)
+       ExpectStatusCode(t,
+               "authenticated non-admin request",
+               PermissionError.HTTPCode,
+               response)
+
+       // Authenticated admin request for nonexistent block.
+       type deletecounter struct {
+               Deleted int `json:"copies_deleted"`
+               Failed  int `json:"copies_failed"`
+       }
+       var response_dc, expected_dc deletecounter
+
+       response = IssueRequest(rest, superuser_nonexistent_block_req)
+       ExpectStatusCode(t,
+               "data manager request, nonexistent block",
+               http.StatusNotFound,
+               response)
+
+       // Authenticated admin request for existing block while never_delete is set.
+       never_delete = true
+       response = IssueRequest(rest, superuser_existing_block_req)
+       ExpectStatusCode(t,
+               "authenticated request, existing block, method disabled",
+               MethodDisabledError.HTTPCode,
+               response)
+       never_delete = false
+
+       // Authenticated admin request for existing block.
+       response = IssueRequest(rest, superuser_existing_block_req)
+       ExpectStatusCode(t,
+               "data manager request, existing block",
+               http.StatusOK,
+               response)
+       // Expect response {"copies_deleted":1,"copies_failed":0}
+       expected_dc = deletecounter{1, 0}
+       json.NewDecoder(response.Body).Decode(&response_dc)
+       if response_dc != expected_dc {
+               t.Errorf("superuser_existing_block_req\nexpected: %+v\nreceived: %+v",
+                       expected_dc, response_dc)
+       }
+       // Confirm the block has been deleted
+       _, err := vols[0].Get(TEST_HASH)
+       var block_deleted = os.IsNotExist(err)
+       if !block_deleted {
+               t.Error("superuser_existing_block_req: block not deleted")
+       }
+}
+
 // ====================
 // Helper functions
 // ====================
similarity index 83%
rename from services/keep/src/keep/handlers.go
rename to services/keepstore/handlers.go
index a5d5e47e09886938701071552adcfc5b2f141f28..9838694ac83b5d5b1db75e2778f8718fead226af 100644 (file)
@@ -39,6 +39,7 @@ func MakeRESTRouter() *mux.Router {
                GetBlockHandler).Methods("GET", "HEAD")
 
        rest.HandleFunc(`/{hash:[0-9a-f]{32}}`, PutBlockHandler).Methods("PUT")
+       rest.HandleFunc(`/{hash:[0-9a-f]{32}}`, DeleteHandler).Methods("DELETE")
 
        // For IndexHandler we support:
        //   /index           - returns all locators
@@ -315,6 +316,87 @@ func GetVolumeStatus(volume string) *VolumeStatus {
        return &VolumeStatus{volume, devnum, free, used}
 }
 
+// DeleteHandler processes DELETE requests.
+//
+// DELETE /{hash:[0-9a-f]{32} will delete the block with the specified hash
+// from all connected volumes.
+//
+// Only the Data Manager, or an Arvados admin with scope "all", are
+// allowed to issue DELETE requests.  If a DELETE request is not
+// authenticated or is issued by a non-admin user, the server returns
+// a PermissionError.
+//
+// Upon receiving a valid request from an authorized user,
+// DeleteHandler deletes all copies of the specified block on local
+// writable volumes.
+//
+// Response format:
+//
+// If the requested blocks was not found on any volume, the response
+// code is HTTP 404 Not Found.
+//
+// Otherwise, the response code is 200 OK, with a response body
+// consisting of the JSON message
+//
+//    {"copies_deleted":d,"copies_failed":f}
+//
+// where d and f are integers representing the number of blocks that
+// were successfully and unsuccessfully deleted.
+//
+func DeleteHandler(resp http.ResponseWriter, req *http.Request) {
+       hash := mux.Vars(req)["hash"]
+       log.Printf("%s %s", req.Method, hash)
+
+       // Confirm that this user is an admin and has a token with unlimited scope.
+       var tok = GetApiToken(req)
+       if tok == "" || !CanDelete(tok) {
+               http.Error(resp, PermissionError.Error(), PermissionError.HTTPCode)
+               return
+       }
+
+       if never_delete {
+               http.Error(resp, MethodDisabledError.Error(), MethodDisabledError.HTTPCode)
+               return
+       }
+
+       // Delete copies of this block from all available volumes.  Report
+       // how many blocks were successfully and unsuccessfully
+       // deleted.
+       var result struct {
+               Deleted int `json:"copies_deleted"`
+               Failed  int `json:"copies_failed"`
+       }
+       for _, vol := range KeepVM.Volumes() {
+               if err := vol.Delete(hash); err == nil {
+                       result.Deleted++
+               } else if os.IsNotExist(err) {
+                       continue
+               } else {
+                       result.Failed++
+                       log.Println("DeleteHandler:", err)
+               }
+       }
+
+       var st int
+
+       if result.Deleted == 0 && result.Failed == 0 {
+               st = http.StatusNotFound
+       } else {
+               st = http.StatusOK
+       }
+
+       resp.WriteHeader(st)
+
+       if st == http.StatusOK {
+               if body, err := json.Marshal(result); err == nil {
+                       resp.Write(body)
+               } else {
+                       log.Printf("json.Marshal: %s (result = %v)\n", err, result)
+                       http.Error(resp, err.Error(), 500)
+               }
+       }
+}
+
 func GetBlock(hash string) ([]byte, error) {
        // Attempt to read the requested hash from a keep volume.
        error_to_caller := NotFoundError
@@ -477,3 +559,23 @@ func IsExpired(timestamp_hex string) bool {
        }
        return time.Unix(ts, 0).Before(time.Now())
 }
+
+// CanDelete returns true if the user identified by api_token is
+// allowed to delete blocks.
+func CanDelete(api_token string) bool {
+       if api_token == "" {
+               return false
+       }
+       // Blocks may be deleted only when Keep has been configured with a
+       // data manager.
+       if data_manager_token == "" {
+               return false
+       }
+       if api_token == data_manager_token {
+               return true
+       }
+       // TODO(twp): look up api_token with the API server
+       // return true if is_admin is true and if the token
+       // has unlimited scope
+       return false
+}
similarity index 88%
rename from services/keep/src/keep/keep.go
rename to services/keepstore/keepstore.go
index c34c268c7f475cdcb3e9e575f2a372878ce80b02..9556185fc064f79712e79e1810cf2c8951818b84 100644 (file)
@@ -53,6 +53,10 @@ var permission_ttl time.Duration
 // Initialized by the --data-manager-token-file flag.
 var data_manager_token string
 
+// never_delete can be used to prevent the DELETE handler from
+// actually deleting anything.
+var never_delete = false
+
 // ==========
 // Error types.
 //
@@ -62,16 +66,17 @@ type KeepError struct {
 }
 
 var (
-       BadRequestError  = &KeepError{400, "Bad Request"}
-       CollisionError   = &KeepError{500, "Collision"}
-       RequestHashError = &KeepError{422, "Hash mismatch in request"}
-       PermissionError  = &KeepError{403, "Forbidden"}
-       DiskHashError    = &KeepError{500, "Hash mismatch in stored data"}
-       ExpiredError     = &KeepError{401, "Expired permission signature"}
-       NotFoundError    = &KeepError{404, "Not Found"}
-       GenericError     = &KeepError{500, "Fail"}
-       FullError        = &KeepError{503, "Full"}
-       TooLongError     = &KeepError{504, "Timeout"}
+       BadRequestError     = &KeepError{400, "Bad Request"}
+       CollisionError      = &KeepError{500, "Collision"}
+       RequestHashError    = &KeepError{422, "Hash mismatch in request"}
+       PermissionError     = &KeepError{403, "Forbidden"}
+       DiskHashError       = &KeepError{500, "Hash mismatch in stored data"}
+       ExpiredError        = &KeepError{401, "Expired permission signature"}
+       NotFoundError       = &KeepError{404, "Not Found"}
+       GenericError        = &KeepError{500, "Fail"}
+       FullError           = &KeepError{503, "Full"}
+       TooLongError        = &KeepError{504, "Timeout"}
+       MethodDisabledError = &KeepError{405, "Method disabled"}
 )
 
 func (e *KeepError) Error() string {
@@ -132,6 +137,12 @@ func main() {
                "Interface on which to listen for requests, in the format "+
                        "ipaddr:port. e.g. -listen=10.0.1.24:8000. Use -listen=:port "+
                        "to listen on all network interfaces.")
+       flag.BoolVar(
+               &never_delete,
+               "never-delete",
+               false,
+               "If set, nothing will be deleted. HTTP 405 will be returned "+
+                       "for valid DELETE requests.")
        flag.StringVar(
                &permission_key_file,
                "permission-key-file",
similarity index 97%
rename from services/keep/src/keep/keep_test.go
rename to services/keepstore/keepstore_test.go
index de189b659c0b10db9cfac8490c689ec87537d48b..b153d6dce27309f3cb692ec0d6a84b724386891a 100644 (file)
@@ -7,6 +7,8 @@ import (
        "os"
        "path"
        "regexp"
+       "sort"
+       "strings"
        "testing"
 )
 
@@ -342,11 +344,14 @@ func TestIndex(t *testing.T) {
        vols[1].Put(TEST_HASH_2+".meta", []byte("metadata"))
 
        index := vols[0].Index("") + vols[1].Index("")
-       expected := `^` + TEST_HASH + `\+\d+ \d+\n` +
+       index_rows := strings.Split(index, "\n")
+       sort.Strings(index_rows)
+       sorted_index := strings.Join(index_rows, "\n")
+       expected := `^\n` + TEST_HASH + `\+\d+ \d+\n` +
                TEST_HASH_3 + `\+\d+ \d+\n` +
-               TEST_HASH_2 + `\+\d+ \d+\n$`
+               TEST_HASH_2 + `\+\d+ \d+$`
 
-       match, err := regexp.MatchString(expected, index)
+       match, err := regexp.MatchString(expected, sorted_index)
        if err == nil {
                if !match {
                        t.Errorf("IndexLocators returned:\n%s", index)
similarity index 94%
rename from services/keep/src/keep/volume.go
rename to services/keepstore/volume.go
index d1956862b10d74d0da40fe7c8809f5bbdb863d4a..b15ef23a85a403c83128bfde5ce18037682f86ca 100644 (file)
@@ -7,14 +7,15 @@ package main
 import (
        "errors"
        "fmt"
-       "strings"
        "os"
+       "strings"
 )
 
 type Volume interface {
        Get(loc string) ([]byte, error)
        Put(loc string, block []byte) error
        Index(prefix string) string
+       Delete(loc string) error
        Status() *VolumeStatus
        String() string
 }
@@ -61,6 +62,14 @@ func (v *MockVolume) Index(prefix string) string {
        return result
 }
 
+func (v *MockVolume) Delete(loc string) error {
+       if _, ok := v.Store[loc]; ok {
+               delete(v.Store, loc)
+               return nil
+       }
+       return os.ErrNotExist
+}
+
 func (v *MockVolume) Status() *VolumeStatus {
        var used uint64
        for _, block := range v.Store {
similarity index 87%
rename from services/keep/src/keep/volume_unix.go
rename to services/keepstore/volume_unix.go
index aafc8debf134ec4bf8c44fbb93d2bc6313892e72..ddddd5abf901d81a86d5bf0a74cab8bc57081a08 100644 (file)
@@ -109,8 +109,7 @@ func (v *UnixVolume) Put(loc string, block []byte) error {
 // corrupted data block.
 //
 func (v *UnixVolume) Read(loc string) ([]byte, error) {
-       blockFilename := filepath.Join(v.root, loc[0:3], loc)
-       buf, err := ioutil.ReadFile(blockFilename)
+       buf, err := ioutil.ReadFile(v.blockPath(loc))
        return buf, err
 }
 
@@ -123,22 +122,22 @@ func (v *UnixVolume) Write(loc string, block []byte) error {
        if v.IsFull() {
                return FullError
        }
-       blockDir := filepath.Join(v.root, loc[0:3])
-       if err := os.MkdirAll(blockDir, 0755); err != nil {
+       bdir := v.blockDir(loc)
+       if err := os.MkdirAll(bdir, 0755); err != nil {
                log.Printf("%s: could not create directory %s: %s",
-                       loc, blockDir, err)
+                       loc, bdir, err)
                return err
        }
 
-       tmpfile, tmperr := ioutil.TempFile(blockDir, "tmp"+loc)
+       tmpfile, tmperr := ioutil.TempFile(bdir, "tmp"+loc)
        if tmperr != nil {
-               log.Printf("ioutil.TempFile(%s, tmp%s): %s", blockDir, loc, tmperr)
+               log.Printf("ioutil.TempFile(%s, tmp%s): %s", bdir, loc, tmperr)
                return tmperr
        }
-       blockFilename := filepath.Join(blockDir, loc)
+       bpath := v.blockPath(loc)
 
        if _, err := tmpfile.Write(block); err != nil {
-               log.Printf("%s: writing to %s: %s\n", v, blockFilename, err)
+               log.Printf("%s: writing to %s: %s\n", v, bpath, err)
                return err
        }
        if err := tmpfile.Close(); err != nil {
@@ -146,8 +145,8 @@ func (v *UnixVolume) Write(loc string, block []byte) error {
                os.Remove(tmpfile.Name())
                return err
        }
-       if err := os.Rename(tmpfile.Name(), blockFilename); err != nil {
-               log.Printf("rename %s %s: %s\n", tmpfile.Name(), blockFilename, err)
+       if err := os.Rename(tmpfile.Name(), bpath); err != nil {
+               log.Printf("rename %s %s: %s\n", tmpfile.Name(), bpath, err)
                os.Remove(tmpfile.Name())
                return err
        }
@@ -230,6 +229,22 @@ func (v *UnixVolume) Index(prefix string) (output string) {
        return
 }
 
+func (v *UnixVolume) Delete(loc string) error {
+       return os.Remove(v.blockPath(loc))
+}
+
+// blockDir returns the fully qualified directory name for the directory
+// where loc is (or would be) stored on this volume.
+func (v *UnixVolume) blockDir(loc string) string {
+       return filepath.Join(v.root, loc[0:3])
+}
+
+// blockPath returns the fully qualified pathname for the path to loc
+// on this volume.
+func (v *UnixVolume) blockPath(loc string) string {
+       return filepath.Join(v.blockDir(loc), loc)
+}
+
 // IsFull returns true if the free space on the volume is less than
 // MIN_FREE_KILOBYTES.
 //