diff --git a/.circleci/config.yml b/.circleci/config.yml index 61357bf13d..d31ad46c96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,8 @@ jobs: - image: avalonmediasystem/avalon:develop environment: - DATABASE_URL=postgresql://postgres@localhost:5432/postgres - - FEDORA_URL=http://localhost:8080/fcrepo/rest + - FEDORA_URL=http://fedoraAdmin:fedoraAdmin@localhost:8080/fcrepo/rest + - FEDORA_BASE_PATH=/test - FEDORA_TIMEOUT=300 - RAILS_ENV=test # Secondary container image on common network. @@ -17,9 +18,9 @@ jobs: - POSTGRES_USER=postgres - POSTGRES_DB=avalon - POSTGRES_PASSWORD=password - - image: ualbertalib/docker-fcrepo4:4.7 + - image: fcrepo/fcrepo:6.5-tomcat9 environment: - CATALINA_OPTS: '-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC' + CATALINA_OPTS: -Dfcrepo.autoversioning.enabled=false - image: zookeeper:3.9 environment: ZOO_ADMINSERVER_ENABLED: false @@ -142,7 +143,7 @@ jobs: default: 4 docker: # Primary container image where all steps run. - - image: avalonmediasystem/avalon:7.5.0-dev-ruby3 + - image: avalonmediasystem/avalon:7.8.0-dev working_directory: /home/app/avalon @@ -165,10 +166,10 @@ workflows: build_test_report: jobs: - build: - ruby_ver: '3.2' - name: 'Ruby3-2' + ruby_ver: '3.3' + name: 'Ruby3-3' parallelism: 4 - upload-coverage: parallelism: 4 requires: - - Ruby3-2 + - Ruby3-3 diff --git a/Dockerfile b/Dockerfile index 0942a04acc..da75951dd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Base stage for building gems -FROM ruby:3.2-bullseye as bundle +FROM ruby:3.3-bullseye as bundle LABEL stage=build LABEL project=avalon RUN apt-get update && apt-get upgrade -y build-essential && apt-get autoremove \ @@ -10,6 +10,7 @@ RUN apt-get update && apt-get upgrade -y build-essential && apt-get autor git \ ffmpeg \ libsqlite3-dev \ + libjemalloc2 \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean @@ -19,8 +20,11 @@ COPY Gemfile.lock ./Gemfile.lock RUN gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" \ && bundle config build.nokogiri --use-system-libraries -ENV RUBY_THREAD_MACHINE_STACK_SIZE 8388608 -ENV RUBY_THREAD_VM_STACK_SIZE 8388608 +ENV RUBY_THREAD_MACHINE_STACK_SIZE 8388608 \ + RUBY_THREAD_VM_STACK_SIZE 8388608 \ + LD_PRELOAD="libjemalloc.so.2" \ + MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true" \ + RUBY_YJIT_ENABLE=1 # Build development gems @@ -33,7 +37,7 @@ RUN bundle config set --local without 'production' \ # Download binaries in parallel -FROM ruby:3.2-bullseye as download +FROM ruby:3.3-bullseye as download LABEL stage=build LABEL project=avalon RUN curl -L https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz | tar xvz -C /usr/bin/ @@ -46,7 +50,7 @@ RUN apt-get -y update && apt-get install -y ffmpeg # Base stage for building final images -FROM ruby:3.2-slim-bullseye as base +FROM ruby:3.3-slim-bullseye as base LABEL stage=build LABEL project=avalon RUN echo "deb http://ftp.us.debian.org/debian/ bullseye main contrib non-free" > /etc/apt/sources.list.d/bullseye.list \ @@ -77,6 +81,7 @@ RUN apt-get update && \ zip \ dumb-init \ libsqlite3-dev \ + libjemalloc2 \ && apt-get -y install mediainfo \ && ln -s /usr/bin/lsof /usr/sbin/ @@ -84,6 +89,10 @@ RUN useradd -m -U app \ && su -s /bin/bash -c "mkdir -p /home/app/avalon" app WORKDIR /home/app/avalon +ENV LD_PRELOAD="libjemalloc.so.2" \ + MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true" \ + RUBY_YJIT_ENABLE=1 + # Build devevelopment image FROM base as dev diff --git a/Gemfile b/Gemfile index e108dde2c8..012299b183 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ gem 'bootsnap', require: false gem 'listen' gem 'net-smtp', require: false gem 'psych', '< 4' -gem 'rails', '~>7.0.8' +gem 'rails', '~>7.2.2' gem 'sprockets', '~>3.7.2' #gem 'sprockets-rails', require: 'sprockets/railtie' gem 'sqlite3' @@ -13,7 +13,7 @@ gem 'sqlite3' gem 'mail', '> 2.8.0.1' # Assets -gem 'bootstrap', '~> 4.0' +gem 'bootstrap', '4.6.2' gem 'coffee-rails', '~> 5.0' gem "font-awesome-rails" gem 'jquery-datatables' @@ -29,11 +29,13 @@ gem 'terser' gem 'shakapacker' # Core Samvera -gem 'active-fedora', '~> 14.0', '>= 14.0.1' -gem 'active_fedora-datastreams', '~> 0.5' -gem 'hydra-head', '~> 12.0' +#gem 'active-fedora', '~> 15.0' +#gem 'active_fedora-datastreams', '~> 0.5' +gem 'active-fedora', git: 'https://github.com/samvera/active_fedora.git', branch: 'fedora6_rebase' +gem 'active_fedora-datastreams', git: 'https://github.com/samvera-labs/active_fedora-datastreams.git', branch: 'fedora6_rebase' +gem 'hydra-head', '~> 13.0' gem 'ldp', '~> 1.1.0' -gem 'noid-rails', '~> 3.1' +gem 'noid-rails', '~> 3.2' gem 'om', git: 'https://github.com/avalonmediasystem/om.git', tag: 'v3.2.0-ruby3' gem 'rdf-rdfxml' @@ -45,11 +47,11 @@ gem 'rsolr', '~> 2.0' # Rails & Samvera Plugins gem 'about_page', git: 'https://github.com/avalonmediasystem/about_page.git', tag: 'avalon-r6.5' -gem 'active_annotations', '~> 0.4' +gem 'active_annotations', '~> 0.5.0' gem 'activerecord-session_store', '>= 2.0.0' gem 'acts_as_list' gem 'api-pagination' -gem 'avalon-about', git: 'https://github.com/avalonmediasystem/avalon-about.git', tag: 'avalon-r7.7' +gem 'avalon-about', git: 'https://github.com/avalonmediasystem/avalon-about.git', tag: 'avalon-r8.0' #gem 'bootstrap-sass', '< 3.4.1' # Pin to less than 3.4.1 due to change in behavior with popovers gem 'bootstrap-toggle-rails' gem 'bootstrap_form' @@ -57,11 +59,11 @@ gem 'iiif_manifest', '~> 1.6' gem 'rack-cors', require: 'rack/cors' gem 'rails_same_site_cookie' gem 'recaptcha', require: 'recaptcha/rails' -gem 'samvera-persona', '~> 0.4', '>= 0.4.1' -gem 'speedy-af', '~> 0.3' +gem 'samvera-persona', '~> 0.5.0' +gem 'speedy-af', git: 'https://github.com/samvera-labs/speedy_af.git', branch: 'empty_reflection' # Avalon Components -gem 'avalon-workflow', git: "https://github.com/avalonmediasystem/avalon-workflow.git", tag: 'avalon-r6.5' +gem 'avalon-workflow', git: "https://github.com/avalonmediasystem/avalon-workflow.git", tag: 'avalon-r8.0' # Authentication & Authorization gem 'devise', '~> 4.8' @@ -71,14 +73,13 @@ gem 'net-ldap' gem 'omniauth', '~> 2.0' gem 'omniauth-identity', '>= 2.0.0' gem 'omniauth-lti', git: "https://github.com/avalonmediasystem/omniauth-lti.git", tag: 'avalon-r4' -gem "omniauth-saml", "~> 2.0" +gem "omniauth-saml", "~> 2.0", ">= 2.2.1" # Media Access & Transcoding -gem 'active_encode', '>= 1.2.2' +gem 'active_encode', '~> 1.2' gem 'audio_waveform-ruby', '~> 1.0.7', require: 'audio_waveform' -gem 'browse-everything', git: "https://github.com/avalonmediasystem/browse-everything.git", branch: 'v1.2-avalon' +gem 'browse-everything', git: "https://github.com/avalonmediasystem/browse-everything.git", branch: 'sharepoint_integration' gem 'fastimage' -gem 'mediainfo', git: "https://github.com/avalonmediasystem/mediainfo.git", tag: 'v0.7.1-avalon' gem 'rest-client', '~> 2.0' gem 'roo' gem 'wavefile', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index a1027bec21..d4e74213ac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,43 +8,35 @@ GIT GIT remote: https://github.com/avalonmediasystem/avalon-about.git - revision: cc8d816b9751d6a04750399d9ef828a33e7ac7eb - tag: avalon-r7.7 + revision: f3106d139d9092ffb0e9ca468fac9e85188ad339 + tag: avalon-r8.0 specs: avalon-about (0.1.0) about_page - mediainfo GIT remote: https://github.com/avalonmediasystem/avalon-workflow.git - revision: 1c188a2be9255ebd8b0b959f04dbf5f888715fb9 - tag: avalon-r6.5 + revision: 503f5fd195359ca73e7facf79843381c611cfa0b + tag: avalon-r8.0 specs: avalon-workflow (0.0.3) GIT remote: https://github.com/avalonmediasystem/browse-everything.git - revision: 51ac41d1631eba86b123222d9bfeef342f25ec56 - branch: v1.2-avalon + revision: eefa721865f392cd3b58f679a9761cbb3e4b5cc9 + branch: sharepoint_integration specs: - browse-everything (1.2.0) + browse-everything (1.4.0) addressable (~> 2.5) aws-sdk-s3 dropbox_api (>= 0.1.20) google-apis-drive_v3 googleauth (>= 0.6.6, < 2.0) - rails (>= 4.2, < 7.1) + rails (>= 4.2, < 8.0) ruby-box signet (~> 0.8) typhoeus -GIT - remote: https://github.com/avalonmediasystem/mediainfo.git - revision: bea9479d33328c6b483ee19c008730f939d98266 - tag: v0.7.1-avalon - specs: - mediainfo (0.7.1) - GIT remote: https://github.com/avalonmediasystem/om.git revision: ffde890b4187a7d8be41ae387264cd6eb20b6ba8 @@ -65,184 +57,225 @@ GIT ims-lti omniauth +GIT + remote: https://github.com/samvera-labs/active_fedora-datastreams.git + revision: 3e1b54f60e6d6316e114f608ee5c50c85d6598c6 + branch: fedora6_rebase + specs: + active_fedora-datastreams (0.5.0) + active-fedora (>= 11.0.0.pre) + activemodel (>= 5.2) + nom-xml (>= 0.5.1) + om (~> 3.1) + rdf (~> 3.2) + rdf-rdfa (= 3.2.0) + rdf-rdfxml (~> 3.2) + +GIT + remote: https://github.com/samvera-labs/speedy_af.git + revision: 223abe9e2a7cdc68b033398bf69922523aeff47e + branch: empty_reflection + specs: + speedy-af (0.3.0) + activesupport (> 5.2) + +GIT + remote: https://github.com/samvera/active_fedora.git + revision: 0f5ccb1536224efec750941ce9a1f58f2e09cd3c + branch: fedora6_rebase + specs: + active-fedora (15.0.1) + active-triples (>= 0.11.0, < 2.0.0) + activemodel (>= 6.1) + activesupport (>= 6.1) + deprecation + faraday (>= 2.0) + faraday-encoding (>= 0.0.5) + faraday-follow_redirects + ldp (>= 0.7.0, < 2) + mutex_m + rsolr (>= 1.1.2, < 3) + ruby-progressbar (~> 1.0) + GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.4) - actionpack (= 7.0.8.4) - activesupport (= 7.0.8.4) + actioncable (7.2.2) + actionpack (= 7.2.2) + activesupport (= 7.2.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.4) - actionpack (= 7.0.8.4) - activejob (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.0.8.4) - actionpack (= 7.0.8.4) - actionview (= 7.0.8.4) - activejob (= 7.0.8.4) - activesupport (= 7.0.8.4) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8.4) - actionview (= 7.0.8.4) - activesupport (= 7.0.8.4) - rack (~> 2.0, >= 2.2.4) + zeitwerk (~> 2.6) + actionmailbox (7.2.2) + actionpack (= 7.2.2) + activejob (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) + mail (>= 2.8.0) + actionmailer (7.2.2) + actionpack (= 7.2.2) + actionview (= 7.2.2) + activejob (= 7.2.2) + activesupport (= 7.2.2) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.2) + actionview (= 7.2.2) + activesupport (= 7.2.2) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.2) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.4) - actionpack (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.2) + actionpack (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.4) - activesupport (= 7.0.8.4) + actionview (7.2.2) + activesupport (= 7.2.2) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - active-fedora (14.0.1) - active-triples (>= 0.11.0, < 2.0.0) - activemodel (>= 5.1) - activesupport (>= 5.1) - deprecation - faraday (>= 1.0) - faraday-encoding (>= 0.0.5) - ldp (>= 0.7.0, < 2) - rsolr (>= 1.1.2, < 3) - ruby-progressbar (~> 1.0) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) active-triples (1.2.0) activemodel (>= 3.0.0) activesupport (>= 3.0.0) rdf (>= 2.0.2, < 4.0) rdf-vocab (>= 2.0, < 4.0) - active_annotations (0.4.0) + active_annotations (0.5.0) json-ld - rails (>= 5.2, < 7.1) + rails (>= 5.2, < 7.3) rdf-vocab (>= 2.1.0) - active_elastic_job (3.2.0) + active_elastic_job (3.3.0) aws-sdk-sqs (~> 1) - rails (>= 5.2.6, < 7.1) - active_encode (1.2.2) + rails (>= 5.2.6, < 8) + active_encode (1.2.3) addressable (~> 2.8) rails - active_fedora-datastreams (0.5.0) - active-fedora (>= 11.0.0.pre) - activemodel (>= 5.2) - nom-xml (>= 0.5.1) - om (~> 3.1) - rdf (~> 3.2) - rdf-rdfxml (~> 3.2) - activejob (7.0.8.4) - activesupport (= 7.0.8.4) + activejob (7.2.2) + activesupport (= 7.2.2) globalid (>= 0.3.6) activejob-traffic_control (0.1.3) activejob (>= 4.2) activesupport (>= 4.2) suo - activejob-uniqueness (0.2.5) - activejob (>= 4.2, < 7.1) - redlock (>= 1.2, < 2) - activemodel (7.0.8.4) - activesupport (= 7.0.8.4) - activerecord (7.0.8.4) - activemodel (= 7.0.8.4) - activesupport (= 7.0.8.4) - activerecord-session_store (2.0.0) - actionpack (>= 5.2.4.1) - activerecord (>= 5.2.4.1) + activejob-uniqueness (0.3.2) + activejob (>= 4.2, < 7.3) + redlock (>= 2.0, < 3) + activemodel (7.2.2) + activesupport (= 7.2.2) + activerecord (7.2.2) + activemodel (= 7.2.2) + activesupport (= 7.2.2) + timeout (>= 0.4.0) + activerecord-session_store (2.1.0) + actionpack (>= 6.1) + activerecord (>= 6.1) + cgi (>= 0.3.6) multi_json (~> 1.11, >= 1.11.2) - rack (>= 2.0.8, < 3) - railties (>= 5.2.4.1) - activestorage (7.0.8.4) - actionpack (= 7.0.8.4) - activejob (= 7.0.8.4) - activerecord (= 7.0.8.4) - activesupport (= 7.0.8.4) + rack (>= 2.0.8, < 4) + railties (>= 6.1) + activestorage (7.2.2) + actionpack (= 7.2.2) + activejob (= 7.2.2) + activerecord (= 7.2.2) + activesupport (= 7.2.2) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8.4) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (7.2.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - acts_as_list (1.1.0) - activerecord (>= 4.2) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - airbrussh (1.4.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + acts_as_list (1.2.2) + activerecord (>= 6.1) + activesupport (>= 6.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + airbrussh (1.5.2) sshkit (>= 1.6.1, != 1.7.0) - api-pagination (5.0.0) + api-pagination (6.0.0) ast (2.4.2) audio_waveform-ruby (1.0.7) json (~> 2.3) - autoprefixer-rails (10.4.7.0) + autoprefixer-rails (10.4.19.0) execjs (~> 2) - aws-eventstream (1.2.0) - aws-partitions (1.801.0) - aws-record (2.10.1) - aws-sdk-dynamodb (~> 1.18) - aws-sdk-cloudfront (1.76.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-core (3.171.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) + aws-eventstream (1.3.0) + aws-partitions (1.976.0) + aws-record (2.13.2) + aws-sdk-dynamodb (~> 1, >= 1.85.0) + aws-sdk-cloudfront (1.96.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sigv4 (~> 1.5) + aws-sdk-core (3.206.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-dynamodb (1.81.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-elastictranscoder (1.40.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-kms (1.63.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-rails (3.7.1) + aws-sdk-dynamodb (1.118.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-elastictranscoder (1.57.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-kms (1.91.0) + aws-sdk-core (~> 3, >= 3.205.0) + aws-sigv4 (~> 1.5) + aws-sdk-rails (4.0.3) + actionmailbox (>= 7.0.0) aws-record (~> 2) - aws-sdk-ses (~> 1) - aws-sdk-sesv2 (~> 1) - aws-sdk-sqs (~> 1) + aws-sdk-s3 (~> 1, >= 1.123.0) + aws-sdk-ses (~> 1, >= 1.50.0) + aws-sdk-sesv2 (~> 1, >= 1.34.0) + aws-sdk-sns (~> 1, >= 1.61.0) + aws-sdk-sqs (~> 1, >= 1.56.0) aws-sessionstore-dynamodb (~> 2) - concurrent-ruby (~> 1) - railties (>= 5.2.0) - aws-sdk-s3 (1.122.0) - aws-sdk-core (~> 3, >= 3.165.0) + concurrent-ruby (~> 1.3, >= 1.3.1) + railties (>= 7.0.0) + aws-sdk-s3 (1.162.0) + aws-sdk-core (~> 3, >= 3.205.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sdk-ses (1.49.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-sesv2 (1.31.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sdk-sqs (1.55.0) - aws-sdk-core (~> 3, >= 3.165.0) - aws-sigv4 (~> 1.1) - aws-sessionstore-dynamodb (2.0.1) - aws-sdk-dynamodb (~> 1) - rack (~> 2) - aws-sigv4 (1.6.0) + aws-sigv4 (~> 1.5) + aws-sdk-ses (1.69.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-sesv2 (1.56.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-sns (1.82.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-sqs (1.80.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sessionstore-dynamodb (2.2.0) + aws-sdk-dynamodb (~> 1, >= 1.85.0) + rack (>= 2, < 4) + rack-session (>= 1, < 3) + aws-sigv4 (1.10.0) aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) - bcrypt (3.1.18) - bigdecimal (3.1.6) + base64 (0.2.0) + bcp47_spec (0.2.1) + bcrypt (3.1.20) + benchmark (0.4.0) + bigdecimal (3.1.8) bindex (0.8.1) bixby (5.0.2) rubocop (= 1.28.2) @@ -250,7 +283,7 @@ GEM rubocop-performance rubocop-rails rubocop-rspec - blacklight (7.33.1) + blacklight (7.38.0) deprecation globalid hashdiff @@ -258,31 +291,31 @@ GEM jbuilder (~> 2.7) kaminari (>= 0.15) ostruct (>= 0.3.2) - rails (>= 5.1, < 7.1) - view_component (~> 2.66) + rails (>= 5.1, < 7.3) + view_component (>= 2.66, < 4) blacklight-access_controls (6.0.1) blacklight (> 6.0, < 8) cancancan (>= 1.8) deprecation (~> 1.0) - bootsnap (1.16.0) + bootsnap (1.18.4) msgpack (~> 1.2) bootstrap (4.6.2) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.16.1, < 2) sassc-rails (>= 2.0.0) bootstrap-toggle-rails (2.2.1.0) - bootstrap_form (5.2.3) - actionpack (>= 6.0) - activemodel (>= 6.0) + bootstrap_form (5.4.0) + actionpack (>= 6.1) + activemodel (>= 6.1) builder (3.3.0) byebug (11.1.3) - cancancan (3.4.0) - capistrano (3.17.3) + cancancan (3.6.1) + capistrano (3.19.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (2.1.0) + capistrano-bundler (2.1.1) capistrano (~> 3.1) capistrano-passenger (0.2.1) capistrano (~> 3.0) @@ -298,15 +331,16 @@ GEM sidekiq (>= 6.0) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + cgi (0.4.1) childprocess (3.0.0) cloudfront-signer (3.0.2) codeclimate-test-reporter (1.0.7) @@ -319,19 +353,19 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.3.3) - config (4.2.1) + concurrent-ruby (1.3.4) + config (5.5.1) deep_merge (~> 1.2, >= 1.2.1) - dry-validation (~> 1.0, >= 1.0.0) - connection_pool (2.3.0) - crack (0.4.5) + connection_pool (2.4.1) + crack (1.0.0) + bigdecimal rexml crass (1.0.6) daemons (1.4.1) - dalli (3.2.3) + dalli (3.2.8) database_cleaner (2.0.2) database_cleaner-active_record (>= 2, < 3) - database_cleaner-active_record (2.0.1) + database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) @@ -340,7 +374,7 @@ GEM deep_merge (1.2.2) deprecation (1.1.0) activesupport - devise (4.9.2) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -349,149 +383,126 @@ GEM devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) - diff-lcs (1.5.0) - docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) + diff-lcs (1.5.1) + docile (1.4.1) + domain_name (0.6.20240107) + dotenv (3.1.2) + dotenv-rails (3.1.2) + dotenv (= 3.1.2) + railties (>= 6.1) + drb (2.2.1) dropbox_api (0.1.21) faraday (< 3.0) oauth2 (~> 1.1) - dry-configurable (1.0.1) - dry-core (~> 1.0, < 2) - zeitwerk (~> 2.6) - dry-core (1.0.0) - concurrent-ruby (~> 1.0) - zeitwerk (~> 2.6) - dry-inflector (1.0.0) - dry-initializer (3.1.1) - dry-logic (1.5.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - zeitwerk (~> 2.6) - dry-schema (1.13.0) - concurrent-ruby (~> 1.0) - dry-configurable (~> 1.0, >= 1.0.1) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-logic (>= 1.5, < 2) - dry-types (>= 1.7, < 2) - zeitwerk (~> 2.6) - dry-types (1.7.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - dry-inflector (~> 1.0, < 2) - dry-logic (>= 1.4, < 2) - zeitwerk (~> 2.6) - dry-validation (1.10.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-schema (>= 1.12, < 2) - zeitwerk (~> 2.6) - ebnf (2.3.2) + ebnf (2.5.0) htmlentities (~> 4.3) - rdf (~> 3.2) + rdf (~> 3.3) scanf (~> 1.0) - sxp (~> 1.2) + sxp (~> 2.0) unicode-types (~> 1.8) edtf (3.1.1) activesupport (>= 3.0, < 8.0) - email_spec (2.2.2) + email_spec (2.3.0) htmlentities (~> 4.3.3) - launchy (~> 2.1) + launchy (>= 2.1, < 4.0) mail (~> 2.7) equivalent-xml (0.6.0) nokogiri (>= 1.4.3) erubi (1.13.0) - et-orbi (1.2.7) + et-orbi (1.2.11) tzinfo ethon (0.16.0) ffi (>= 1.15.0) - execjs (2.8.1) - factory_bot (6.2.1) + execjs (2.9.1) + factory_bot (6.4.6) activesupport (>= 5.0.0) - factory_bot_rails (6.2.0) - factory_bot (~> 6.2.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) railties (>= 5.0.0) fakefs (2.5.0) - faker (3.2.0) + faker (3.4.2) i18n (>= 1.8.11, < 2) - faraday (2.7.4) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-encoding (0.0.5) + faraday (2.12.0) + faraday-net_http (>= 2.0, < 3.4) + json + logger + faraday-encoding (0.0.6) faraday - faraday-net_http (3.0.2) - fastimage (2.2.7) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) + faraday-net_http (3.3.0) + net-http + fastimage (2.3.1) fcrepo_wrapper (0.9.0) ruby-progressbar - ffi (1.15.5) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) + ffi (1.17.0) + ffi-compiler (1.3.2) + ffi (>= 1.15.5) rake font-awesome-rails (4.7.0.8) railties (>= 3.2, < 8.0) - fugit (1.8.1) - et-orbi (~> 1, >= 1.2.7) + fugit (1.11.1) + et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) google-analytics-rails (1.1.0) - google-apis-core (0.11.0) + google-apis-core (0.15.1) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) mini_mime (~> 1.0) + mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-drive_v3 (0.37.0) - google-apis-core (>= 0.11.0, < 2.a) - googleauth (1.5.0) - faraday (>= 0.17.3, < 3.a) + google-apis-drive_v3 (0.59.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-env (2.2.1) + faraday (>= 1.0, < 3.a) + googleauth (1.11.2) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - hashdiff (1.0.1) + haml (5.2.2) + temple (>= 0.8.0) + tilt + hashdiff (1.1.1) hashie (5.0.0) hooks (0.4.1) uber (~> 0.0.14) htmlentities (4.3.4) - http-2-next (1.0.3) - http (5.1.1) + http-2 (1.0.1) + http (5.2.0) addressable (~> 2.8) + base64 (~> 0.1) http-cookie (~> 1.0) http-form_data (~> 2.2) - llhttp-ffi (~> 0.4.0) + llhttp-ffi (~> 0.5.0) http-accept (1.7.0) - http-cookie (1.0.5) + http-cookie (1.0.7) domain_name (~> 0.5) http-form_data (2.3.0) http_logger (0.7.0) httpclient (2.8.3) - httpx (1.2.2) - http-2-next (>= 1.0.3) - hydra-access-controls (12.1.0) + httpx (1.3.1) + http-2 (>= 1.0.0) + hydra-access-controls (13.0.0) active-fedora (>= 10.0.0) - activesupport (>= 5.2, < 7.1) + activesupport (>= 6.1, < 8.0) blacklight-access_controls (~> 6.0) cancancan (>= 1.8, < 4) deprecation (~> 1.0) - hydra-core (12.1.0) - hydra-access-controls (= 12.1.0) - railties (>= 5.2, < 7.1) - hydra-head (12.1.0) - hydra-access-controls (= 12.1.0) - hydra-core (= 12.1.0) - rails (>= 5.2, < 7.1) - i18n (1.14.5) + hydra-core (13.0.0) + hydra-access-controls (= 13.0.0) + railties (>= 6.1, < 8.0) + hydra-head (13.0.0) + hydra-access-controls (= 13.0.0) + hydra-core (= 13.0.0) + rails (>= 6.1, < 8.0) + i18n (1.14.6) concurrent-ruby (~> 1.0) iconv (1.0.8) iiif_manifest (1.6.0) @@ -499,7 +510,11 @@ GEM ims-lti (1.1.13) builder oauth (>= 0.4.5, < 0.6) - jbuilder (2.11.5) + io-console (0.7.2) + irb (1.14.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jbuilder (2.12.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.2) @@ -510,16 +525,18 @@ GEM thor (>= 0.14, < 2.0) jquery-ui-rails (7.0.0) railties (>= 3.2.16) - json (2.6.3) - json-canonicalization (0.3.1) - json-ld (3.2.3) + json (2.7.2) + json-canonicalization (1.0.0) + json-ld (3.3.2) htmlentities (~> 4.3) - json-canonicalization (~> 0.3) + json-canonicalization (~> 1.0) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) - rack (~> 2.2) - rdf (~> 3.2, >= 3.2.9) - jwt (2.7.0) + rack (>= 2.2, < 4) + rdf (~> 3.3) + rexml (~> 3.2) + jwt (2.9.3) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -546,13 +563,14 @@ GEM rdf-vocab (>= 0.8) slop link_header (0.0.8) - listen (3.8.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - llhttp-ffi (0.4.0) + llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) - lograge (0.13.0) + logger (1.6.1) + lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -571,40 +589,45 @@ GEM unf marcel (1.0.4) matrix (0.4.2) - memoist (0.16.2) method_source (1.1.0) - mime-types (3.4.1) + mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) + mime-types-data (3.2024.0820) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitar (0.9) - minitest (5.24.1) - msgpack (1.6.0) + minitar (1.0.2) + minitest (5.25.1) + msgpack (1.7.2) multi_json (1.15.0) - multi_xml (0.6.0) - multipart-post (2.3.0) - mysql2 (0.5.5) - net-imap (0.4.14) + multi_xml (0.7.1) + bigdecimal (~> 3.1) + multipart-post (2.4.1) + mutex_m (0.2.0) + mysql2 (0.5.6) + net-http (0.4.1) + uri + net-imap (0.4.16) date net-protocol - net-ldap (0.18.0) + net-ldap (0.19.0) net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) net-smtp (0.5.0) net-protocol - net-ssh (7.0.1) + net-ssh (7.2.3) netrc (0.11.0) nio4r (2.7.3) noid (0.9.0) - noid-rails (3.1.0) - actionpack (>= 5.0.0, < 7.1) + noid-rails (3.2.0) + actionpack (>= 5.0.0, < 8) noid (~> 0.9) - nokogiri (1.16.6) + nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) nom-xml (1.2.0) @@ -617,66 +640,74 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 4) - okcomputer (1.18.4) - omniauth (2.1.1) + okcomputer (1.18.5) + omniauth (2.1.2) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection omniauth-identity (3.0.9) bcrypt omniauth - omniauth-saml (2.1.0) - omniauth (~> 2.0) - ruby-saml (~> 1.12) + omniauth-saml (2.2.1) + omniauth (~> 2.1) + ruby-saml (~> 1.17) orm_adapter (0.5.0) os (1.1.4) - ostruct (0.5.5) - parallel (1.23.0) - paranoia (2.6.1) - activerecord (>= 5.1, < 7.1) - parser (3.2.1.0) + ostruct (0.6.0) + package_json (0.1.0) + parallel (1.26.3) + paranoia (3.0.0) + activerecord (>= 6, < 8.1) + parser (3.3.4.2) ast (~> 2.4.1) - pg (1.5.3) + racc + pg (1.5.7) popper_js (1.16.1) - pretender (0.4.0) - actionpack (>= 5.2) + pretender (0.5.0) + actionpack (>= 6.1) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) - pry-rails (0.3.9) - pry (>= 0.10.4) + pry-rails (0.3.11) + pry (>= 0.13.0) psych (3.3.4) - public_suffix (5.0.1) - puma (6.4.2) + public_suffix (6.0.1) + puma (6.4.3) nio4r (~> 2.0) raabro (1.4.0) - racc (1.8.0) - rack (2.2.9) + racc (1.8.1) + rack (2.2.10) rack-cors (2.0.2) rack (>= 2.0.0) - rack-protection (3.0.5) - rack - rack-proxy (0.7.6) + rack-protection (3.2.0) + base64 (>= 0.1.0) + rack (~> 2.2, >= 2.2.4) + rack-proxy (0.7.7) rack + rack-session (1.0.2) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8.4) - actioncable (= 7.0.8.4) - actionmailbox (= 7.0.8.4) - actionmailer (= 7.0.8.4) - actionpack (= 7.0.8.4) - actiontext (= 7.0.8.4) - actionview (= 7.0.8.4) - activejob (= 7.0.8.4) - activemodel (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) + rackup (1.0.0) + rack (< 3) + webrick + rails (7.2.2) + actioncable (= 7.2.2) + actionmailbox (= 7.2.2) + actionmailer (= 7.2.2) + actionpack (= 7.2.2) + actiontext (= 7.2.2) + actionview (= 7.2.2) + activejob (= 7.2.2) + activemodel (= 7.2.2) + activerecord (= 7.2.2) + activestorage (= 7.2.2) + activesupport (= 7.2.2) bundler (>= 1.15.0) - railties (= 7.0.8.4) + railties (= 7.2.2) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -691,73 +722,92 @@ GEM rails_same_site_cookie (0.1.9) rack (>= 1.5) user_agent_parser (~> 2.6) - railties (7.0.8.4) - actionpack (= 7.0.8.4) - activesupport (= 7.0.8.4) - method_source + railties (7.2.2) + actionpack (= 7.2.2) + activesupport (= 7.2.2) + irb (~> 1.13) + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) rb-readline (0.5.5) - rdf (3.2.11) + rdf (3.3.2) + bcp47_spec (~> 0.2) + bigdecimal (~> 3.1, >= 3.1.5) link_header (~> 0.0, >= 0.0.8) - rdf-isomorphic (3.2.1) - rdf (~> 3.2) + rdf-aggregate-repo (3.3.0) + rdf (~> 3.3) + rdf-isomorphic (3.3.0) + rdf (~> 3.3) rdf-ldp (0.1.0) deprecation rdf - rdf-rdfxml (3.2.2) - builder (~> 3.2) + rdf-rdfa (3.2.0) + haml (~> 5.2) htmlentities (~> 4.3) rdf (~> 3.2) + rdf-aggregate-repo (~> 3.2) + rdf-vocab (~> 3.2) rdf-xsd (~> 3.2) - rdf-turtle (3.2.1) - ebnf (~> 2.3) - rdf (~> 3.2) - rdf-vocab (3.2.3) - rdf (~> 3.2, >= 3.2.4) - rdf-xsd (3.2.1) - rdf (~> 3.2) + rdf-rdfxml (3.3.0) + builder (~> 3.2, >= 3.2.4) + htmlentities (~> 4.3) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + rdf-turtle (3.3.1) + base64 (~> 0.2) + bigdecimal (~> 3.1, >= 3.1.5) + ebnf (~> 2.5) + rdf (~> 3.3) + rdf-vocab (3.3.1) + rdf (~> 3.3) + rdf-xsd (3.3.0) + rdf (~> 3.3) rexml (~> 3.2) - react-rails (2.7.1) + rdoc (6.3.4.1) + react-rails (3.2.1) babel-transpiler (>= 0.7.0) connection_pool execjs railties (>= 3.2) tilt - recaptcha (5.14.0) + recaptcha (5.17.0) redis (4.8.1) - redis-actionpack (5.3.0) + redis-actionpack (5.4.0) actionpack (>= 5, < 8) - redis-rack (>= 2.1.0, < 3) + redis-rack (>= 2.1.0, < 4) redis-store (>= 1.1.0, < 2) redis-activesupport (5.3.0) activesupport (>= 3, < 8) redis-store (>= 1.3, < 2) - redis-rack (2.1.4) - rack (>= 2.0.8, < 3) + redis-client (0.22.2) + connection_pool + redis-rack (3.0.0) + rack-session (>= 0.2.0) redis-store (>= 1.2, < 2) redis-rails (5.0.2) redis-actionpack (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6) redis-store (>= 1.2, < 2) - redis-store (1.9.1) - redis (>= 4, < 5) - redlock (1.3.2) - redis (>= 3.0.0, < 6.0) - regexp_parser (2.7.0) + redis-store (1.11.0) + redis (>= 4, < 6) + redlock (2.0.6) + redis-client (>= 0.14.1, < 1.0.0) + regexp_parser (2.9.2) + reline (0.5.10) + io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) - request_store (1.5.1) + request_store (1.7.0) rack (>= 1.4) - responders (3.1.0) + responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) rest-client (2.1.0) @@ -766,36 +816,35 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (3.1.2) - rexml (3.3.1) - strscan - roo (2.10.0) + rexml (3.3.9) + roo (2.10.1) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) - rsolr (2.5.0) + rsolr (2.6.0) builder (>= 2.1.2) faraday (>= 0.9, < 3, != 2.0.0) - rspec-core (3.12.1) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.2) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) + rspec-support (~> 3.13.0) rspec-its (1.3.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) - rspec-mocks (3.12.3) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-rails (6.0.3) + rspec-support (~> 3.13.0) + rspec-rails (6.1.4) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) - rspec-core (~> 3.12) - rspec-expectations (~> 3.12) - rspec-mocks (~> 3.12) - rspec-support (~> 3.12) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) rspec-retry (0.6.2) rspec-core (> 3.3) - rspec-support (3.12.0) + rspec-support (3.13.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) rubocop (1.28.2) @@ -807,9 +856,9 @@ GEM rubocop-ast (>= 1.17.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.24.1) - parser (>= 3.1.1.0) - rubocop-performance (1.16.0) + rubocop-ast (1.32.1) + parser (>= 3.3.1.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.15.2) @@ -824,17 +873,16 @@ GEM multipart-post oauth2 ruby-progressbar (1.13.0) - ruby-saml (1.15.0) + ruby-saml (1.17.0) nokogiri (>= 1.13.10) rexml - ruby2_keywords (0.0.5) rubyzip (1.3.0) - samvera-persona (0.4.1) + samvera-persona (0.5.0) devise (~> 4.6) devise_invitable (>= 1.7, < 3.0) - paranoia (~> 2.2) + paranoia (~> 3.0) pretender - rails (>= 5.2.4.3, < 7.1) + rails (>= 5.2.4.3, < 8.0) sass (3.4.22) sassc (2.4.0) ffi (~> 1.9) @@ -846,28 +894,30 @@ GEM tilt scanf (1.0.0) scrub_rb (1.0.1) + securerandom (0.3.1) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (3.0.0) - sequel (5.77.0) + sequel (5.83.1) bigdecimal - shakapacker (7.0.2) + shakapacker (8.0.1) activesupport (>= 5.2) + package_json rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - shoulda-matchers (5.3.0) + shoulda-matchers (6.4.0) activesupport (>= 5.2.0) sidekiq (6.5.12) connection_pool (>= 2.2.5, < 3) rack (~> 2.0) redis (>= 4.5.0, < 5) - sidekiq-cron (1.10.1) + sidekiq-cron (1.12.0) fugit (~> 1.8) globalid (>= 1.0.1) sidekiq (>= 6) - signet (0.17.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -890,63 +940,63 @@ GEM nokogiri stomp xml-simple - speedy-af (0.3.0) - active-fedora (>= 11.0.0) - activesupport (> 5.2) - sprockets (3.7.2) + sprockets (3.7.3) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-es6 (0.9.2) babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - sqlite3 (1.6.3) + sqlite3 (2.0.4) mini_portile2 (~> 2.8.0) - sshkit (1.21.3) + sshkit (1.23.0) + base64 net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) net-ssh (>= 2.8.0) stomp (1.4.10) - strscan (3.1.0) suo (0.4.0) dalli msgpack redis - sxp (1.2.3) + sxp (2.0.0) matrix (~> 0.4) - rdf (~> 3.2) - terser (1.2.0) + rdf (~> 3.3) + temple (0.10.3) + terser (1.2.3) execjs (>= 0.3.0, < 3) - thor (1.3.1) - tilt (2.0.11) + thor (1.3.2) + tilt (2.4.0) timeout (0.4.1) trailblazer-option (0.1.2) twitter-typeahead-rails (0.11.1.pre.corejavascript) actionpack (>= 3.1) jquery-rails railties (>= 3.1) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.0.15) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - unicode-types (1.8.0) - user_agent_parser (2.14.0) - view_component (2.83.0) + unf (0.2.0) + unicode-display_width (2.5.0) + unicode-types (1.10.0) + uri (0.13.1) + user_agent_parser (2.18.0) + useragent (0.16.10) + view_component (3.14.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) warden (1.2.9) rack (>= 2.0.9) wavefile (1.0.1) - web-console (4.2.0) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) @@ -955,11 +1005,11 @@ GEM nokogiri (~> 1.6) rubyzip (~> 1.0) selenium-webdriver (~> 3.0) - webmock (3.18.1) + webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.8.1) + webrick (1.8.2) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -968,7 +1018,7 @@ GEM rexml xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.16) + zeitwerk (2.6.18) zk (1.10.0) zookeeper (~> 1.5.0) zookeeper (1.5.5) @@ -979,11 +1029,11 @@ PLATFORMS DEPENDENCIES about_page! - active-fedora (~> 14.0, >= 14.0.1) - active_annotations (~> 0.4) + active-fedora! + active_annotations (~> 0.5.0) active_elastic_job - active_encode (>= 1.2.2) - active_fedora-datastreams (~> 0.5) + active_encode (~> 1.2) + active_fedora-datastreams! activejob-traffic_control activejob-uniqueness activerecord-session_store (>= 2.0.0) @@ -1004,7 +1054,7 @@ DEPENDENCIES blacklight (~> 7.25) blacklight-access_controls (>= 6.0.1) bootsnap - bootstrap (~> 4.0) + bootstrap (= 4.6.2) bootstrap-toggle-rails bootstrap_form browse-everything! @@ -1037,7 +1087,7 @@ DEPENDENCIES hashdiff (>= 1.0) hooks httpx - hydra-head (~> 12.0) + hydra-head (~> 13.0) iconv (~> 1.0.6) iiif_manifest (~> 1.6) ims-lti (~> 1.1.13) @@ -1050,17 +1100,16 @@ DEPENDENCIES lograge mail (> 2.8.0.1) marc - mediainfo! mysql2 net-ldap net-smtp - noid-rails (~> 3.1) + noid-rails (~> 3.2) okcomputer om! omniauth (~> 2.0) omniauth-identity (>= 2.0.0) omniauth-lti! - omniauth-saml (~> 2.0) + omniauth-saml (~> 2.0, >= 2.2.1) parallel pg pry-byebug @@ -1068,7 +1117,7 @@ DEPENDENCIES psych (< 4) puma (>= 6.4.2) rack-cors - rails (~> 7.0.8) + rails (~> 7.2.2) rails-controller-testing rails_same_site_cookie rb-readline @@ -1084,7 +1133,7 @@ DEPENDENCIES rspec-rails rspec-retry rspec_junit_formatter - samvera-persona (~> 0.4, >= 0.4.1) + samvera-persona (~> 0.5.0) sass (= 3.4.22) selenium-webdriver sequel @@ -1094,7 +1143,7 @@ DEPENDENCIES sidekiq-cron (~> 1.9) simplecov solr_wrapper (>= 0.16) - speedy-af (~> 0.3) + speedy-af! sprockets (~> 3.7.2) sprockets-es6 sqlite3 diff --git a/app/assets/javascripts/player_listeners.js b/app/assets/javascripts/player_listeners.js index fb66f3fceb..858b28f440 100644 --- a/app/assets/javascripts/player_listeners.js +++ b/app/assets/javascripts/player_listeners.js @@ -17,6 +17,7 @@ let canvasIndex = -1; let currentSectionLabel = undefined; let addToPlaylistListenersAdded = false; +let searchFieldListenerAdded = false let firstLoad = true; let streamId = ''; let isMobile = false; @@ -35,6 +36,51 @@ let isPlaying = false; function addActionButtonListeners(player, mediaObjectId, sectionIds) { if (player && player.player != undefined) { let currentIndex = parseInt(player.dataset.canvasindex); + /* Ensure we only add player listeners once */ + if (firstLoad === true) { + /* Add player event listeners to update UI components on the page */ + // Listen to 'seeked' event to udate add to playlist form when using while media is playing or manually seeking + player.player.on('seeked', () => { + if (getActiveItem() != undefined) { + activeTrack = getActiveItem(false); + if (activeTrack != undefined) { + streamId = activeTrack.streamId; + } + disableEnableCurrentTrack(activeTrack, player.player.currentTime(), isPlaying, currentSectionLabel); + } + }); + + player.player.on('play', () => { isPlaying = true; }); + + player.player.on('pause', () => { isPlaying = false; }); + + /* + Disable action buttons tied to player related information on player's 'emptied' event which functions + parallel to the player's src changes. So, that the user doesn't interact with them get corrupted data + in the UI when player is loading the new section media into it. + Once the player is fully loaded these buttons are enabled as needed. + */ + player.player.on('emptied', () => { + resetAllActionButtons(); + }); + + /* + Enable action buttons on player's 'loadstart' event which functions parallel to the player's src changes. + Sometimes the player event to disable the buttons is triggered after the function to enable the buttons, + resulting in the buttons never enabling. Since the enabling of the action buttons occurs before the player + is emptied, it is also possible that the information populating the buttons is for the old canvas, so we + run `buildActionButtons` again rather than just enabling the buttons. + */ + player.player.on('loadstart', () => { + let addToPlaylistBtn = document.getElementById('addToPlaylistBtn'); + let thumbnailBtn = document.getElementById('thumbnailBtn'); + let timelineBtn = document.getElementById('timelineBtn'); + + if (addToPlaylistBtn.disabled && thumbnailBtn.disabled && timelineBtn.disabled) { + buildActionButtons(player, mediaObjectId, sectionIds); + } + }); + } /* For both Android and iOS, player.readyState() is 0 until media playback is started. Therefore, use player.src() to check whether there's a playable media @@ -69,32 +115,6 @@ function addActionButtonListeners(player, mediaObjectId, sectionIds) { resetAllActionButtons(); } - /* Add player event listeners to update UI components on the page */ - // Listen to 'seeked' event to udate add to playlist form - player.player.on('seeked', () => { - if (getActiveItem() != undefined) { - activeTrack = getActiveItem(false); - if (activeTrack != undefined) { - streamId = activeTrack.streamId; - } - disableEnableCurrentTrack(activeTrack, player.player.currentTime(), isPlaying, currentSectionLabel); - } - }); - - player.player.on('play', () => { isPlaying = true; }); - - player.player.on('pause', () => { isPlaying = false; }); - - /* - Disable action buttons tied to player related information on player's 'loadstart' event which functions - parallel to the player's src changes. So, that the user doesn't interact with them get corrupted data - in the UI when player is loading the new section media into it. - Once the player is fully loaded these buttons are enabled as needed. - */ - player.player.on('loadstart', () => { - resetAllActionButtons(); - }); - // Collapse sub-panel related to the selected option in the add to playlist form when it is collapsed let playlistSection = $('#playlistitem_scope_section'); let playlistTrack = $('#playlistitem_scope_track'); @@ -324,6 +344,23 @@ function addToPlaylistListeners(sectionIds, mediaObjectId) { } }); + // Set playlist search box to readonly in mobile browsers to prevent + // keyboard from popping up when opening playlist dropdown. + $('.select2-selection').on("click", function () { + const IS_TOUCH_ONLY = navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && !window.matchMedia("(pointer: fine").matches; + let searchField = $('.select2-search__field'); + if ((/Mobi|iPhone/i.test(window.navigator.userAgent) || IS_TOUCH_ONLY) && searchField.length > 0) { + searchField.attr('readonly', 'readonly') + if (!searchFieldListenerAdded) { + searchField.on('click', function(e) { + searchField.removeAttr('readonly').select(); + }); + + searchFieldListenerAdded = true + } + } + }); + addToPlaylistListenersAdded = true; } diff --git a/app/assets/javascripts/supplemental_files.js b/app/assets/javascripts/supplemental_files.js index 891efcd16f..f8b68237ff 100644 --- a/app/assets/javascripts/supplemental_files.js +++ b/app/assets/javascripts/supplemental_files.js @@ -73,6 +73,8 @@ $('.supplemental-file-form') .on('ajax:success', (event, data, status, xhr) => { var $row = $(event.currentTarget.parentElement); const { masterfileId, fileId } = event.currentTarget.dataset; + // Get machine-generated checkbox input on form submission + var isMachineGen = $row.find(`input[id="machine_generated_${fileId}"]`)[0].checked; // Set the label to the new value var newLabel = $row @@ -90,6 +92,10 @@ $('.supplemental-file-form') // Show flash message for success $row.find('.message-content').html('Successfully updated.'); + // Show/hide icon based on the updated machine-generated form check + isMachineGen + ? $row.find('.fa-gears').removeClass('d-none') + : $row.find('.fa-gears').addClass('d-none'); $row.find('.icon-success').removeClass('d-none'); $row.find('.visible-inline').addClass('alert'); }) diff --git a/app/assets/stylesheets/avalon.scss b/app/assets/stylesheets/avalon.scss index 2a686d88f2..0ce28ecb98 100644 --- a/app/assets/stylesheets/avalon.scss +++ b/app/assets/stylesheets/avalon.scss @@ -935,6 +935,9 @@ h5.card-title { .form-group { margin-bottom: 0.2rem; } + .fa-gears { + display: none; + } display: flex; } margin-top: 0.75rem; diff --git a/app/assets/stylesheets/avalon/_modals.scss b/app/assets/stylesheets/avalon/_modals.scss index e865cc55d8..6ba97fa9cc 100644 --- a/app/assets/stylesheets/avalon/_modals.scss +++ b/app/assets/stylesheets/avalon/_modals.scss @@ -37,21 +37,25 @@ .special-access label { display: block; - - input, - select { - display: inline; - width: auto; - min-width: 300px; - } - p { margin: 10px 0 5px 0; } + input, + select, .twitter-typeahead { - width: auto; + width: fit-content; + min-width: 375px; } + + input.form-control.date-input { + width: fit-content; + min-width: 0; + } + } + + .special-access .d-flex { + gap: 0.5em; } } diff --git a/app/assets/stylesheets/blacklight.scss b/app/assets/stylesheets/blacklight.scss index 0615b155f4..3729d2fee0 100644 --- a/app/assets/stylesheets/blacklight.scss +++ b/app/assets/stylesheets/blacklight.scss @@ -65,4 +65,4 @@ img.no-icon { .page-item span.page-link { color: $dark !important; -} +} \ No newline at end of file diff --git a/app/controllers/bookmarks_controller.rb b/app/controllers/bookmarks_controller.rb index f6938628dd..a6697fe514 100644 --- a/app/controllers/bookmarks_controller.rb +++ b/app/controllers/bookmarks_controller.rb @@ -157,7 +157,7 @@ def status_action documents else case params['action'] when 'publish' - if media_object.title.nil? || media_object.date_issued.nil? + if media_object.title.nil? errors += ["#{id}, Unable to Publish Item. Missing required fields."] else success_ids << id diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 3371bbb4db..19eb71c8e9 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -102,6 +102,8 @@ class CatalogController < ApplicationController config.add_facet_field 'has_captions_bsi', label: 'Has Captions', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript config.add_facet_field 'has_transcripts_bsi', label: 'Has Transcripts', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript + config.add_facet_field 'subject_ssim', label: 'Subject', if: false + # Have BL send all facet field names to Solr, which has been the default # previously. Simply remove these lines if you'd rather use Solr request # handler defaults, or have no facets. @@ -113,7 +115,8 @@ class CatalogController < ApplicationController # solr fields to be displayed in the index (search results) view # The ordering of the field names is the order of the display config.add_index_field 'title_tesi', label: 'Title', if: Proc.new {|context, _field_config, _document| context.request.format == :json } - config.add_index_field 'date_issued_ssi', label: 'Date', helper_method: :combined_display_date + config.add_index_field 'date_issued_ssi', label: 'Date', if: Proc.new {|_context, _field_config, document| document[:date_issued_ssi].present? } + config.add_index_field 'date_created_ssi', label: 'Date', if: Proc.new {|_context, _field_config, document| document[:date_created_ssi].present? && document[:date_issued_ssi].blank? } config.add_index_field 'creator_ssim', label: 'Main contributors', helper_method: :contributor_index_display config.add_index_field 'abstract_ssi', label: 'Summary', helper_method: :description_index_display config.add_index_field 'duration_ssi', label: 'Duration', if: Proc.new {|context, _field_config, _document| context.request.format == :json } diff --git a/app/controllers/encode_records_controller.rb b/app/controllers/encode_records_controller.rb index 263c81d1d3..a7e5646913 100644 --- a/app/controllers/encode_records_controller.rb +++ b/app/controllers/encode_records_controller.rb @@ -71,7 +71,7 @@ def paged_index "recordsFiltered": filtered_records_total, "data": @encode_records.collect do |encode| encode_presenter = EncodePresenter.new(encode) - encode_status = encode_presenter.status.downcase + encode_status = encode_presenter.status&.downcase unless encode_status == 'completed' progress_class = 'progress-bar-striped' end @@ -117,7 +117,7 @@ def set_encode_record def format_progress(presenter) # Set progress = 100.0 when job failed - if presenter.status.casecmp("failed") == 0 + if presenter.status&.casecmp("failed") == 0 100.0 else presenter.progress diff --git a/app/controllers/master_files_controller.rb b/app/controllers/master_files_controller.rb index 1589b5d0ca..4e8d1f06df 100644 --- a/app/controllers/master_files_controller.rb +++ b/app/controllers/master_files_controller.rb @@ -246,6 +246,8 @@ def get_frame end if content send_data content, :filename => "#{params[:type]}-#{@master_file.id.split(':')[1]}", :disposition => :inline, :type => mimeType + elsif @master_file.is_video? + redirect_to ActionController::Base.helpers.asset_path('video_icon.png') else redirect_to ActionController::Base.helpers.asset_path('audio_icon.png') end @@ -342,7 +344,9 @@ def download_derivative # Use an AWS presigned URL to facilitate direct download of the derivative to avoid # having to download the file to the server as a tmp file and then sending that to # the client. Doing this reduces latency and server load. - redirect_to FileLocator::S3File.new(path).download_url + # Rails 7.0 adds a config option to protect against "open redirects". We override + # that here in case the s3 bucket is not local. + redirect_to FileLocator::S3File.new(path).download_url, allow_other_host: true else send_file path, filename: File.basename(path), disposition: 'attachment' end @@ -366,10 +370,10 @@ def set_masterfile end def set_masterfile_proxy - if params[:id].blank? || SpeedyAF::Proxy::MasterFile.find(params[:id]).nil? + @master_file = SpeedyAF::Proxy::MasterFile.find(params[:id], load_reflections: true) + if params[:id].blank? || @master_file.nil? flash[:notice] = "MasterFile #{params[:id]} does not exist" end - @master_file = SpeedyAF::Proxy::MasterFile.find(params[:id]) end # return deflated waveform content. deflate only if necessary diff --git a/app/controllers/media_objects_controller.rb b/app/controllers/media_objects_controller.rb index 0d6a36c329..8ed6e67940 100644 --- a/app/controllers/media_objects_controller.rb +++ b/app/controllers/media_objects_controller.rb @@ -203,7 +203,7 @@ def update_media_object error_messages = [] unless @media_object.valid? invalid_fields = @media_object.errors.attribute_names - required_fields = [:title, :date_issued] + required_fields = [:title] unless required_fields.any? { |f| invalid_fields.include? f } invalid_fields.each do |field| #NOTE this will erase all values for fields with multiple values @@ -230,7 +230,15 @@ def update_media_object master_file.date_digitized = DateTime.parse(file_spec[:date_digitized]).to_time.utc.iso8601 if file_spec[:date_digitized].present? master_file.identifier += Array(params[:files][index][:other_identifier]) master_file.comment += Array(params[:files][index][:comment]) - master_file.media_object = @media_object + begin + master_file.media_object = @media_object + rescue ActiveFedora::Rollback => e + file_location = file_spec.dig(:file_location) || '' + message = "Problem saving MasterFile for #{file_location}:" + error_messages += [message] + error_messages += [e.message] + break + end if file_spec[:files].present? if master_file.update_derivatives(file_spec[:files], false) master_file.update_stills_from_offset! @@ -413,7 +421,7 @@ def update_status begin case status when 'publish' - unless media_object.title.present? && media_object.date_issued.present? + unless media_object.title.present? errors += ["#{media_object&.title} (#{id}) (missing required fields)"] next end diff --git a/app/controllers/supplemental_files_controller.rb b/app/controllers/supplemental_files_controller.rb index 8b1adf6430..08fd53be77 100644 --- a/app/controllers/supplemental_files_controller.rb +++ b/app/controllers/supplemental_files_controller.rb @@ -87,7 +87,9 @@ def show if Settings.supplemental_files.proxy send_data @supplemental_file.file.download, filename: @supplemental_file.file.filename.to_s, type: @supplemental_file.file.content_type, disposition: 'attachment' else - redirect_to rails_blob_path(@supplemental_file.file, disposition: "attachment") + # Rails 7.0 adds a config option to protect against "open redirects". We override + # that here in case the active storage db is not local. + redirect_to rails_blob_path(@supplemental_file.file, disposition: "attachment"), allow_other_host: true end } format.json { render json: @supplemental_file.as_json } diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 1205dd554f..d016d51199 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -15,10 +15,7 @@ class Users::SessionsController < Devise::SessionsController def new if Avalon::Authentication::VisibleProviders.length == 1 && params[:admin].blank? - omniauth_params = params.reject { |k,v| ['controller','action'].include?(k) } - omniauth_params.permit! - login_path = user_omniauth_authorize_path(Avalon::Authentication::VisibleProviders.first[:provider], omniauth_params) - redirect_to login_path + render :omniauth_new, layout: false else super end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2f3e8e3c7f..789d996260 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -139,8 +139,9 @@ def user_key current_user.user_key if current_user end - # the mediainfo gem returns duration as milliseconds - # see attr_reader.rb line 48 in the mediainfo source + # We are converting FFprobe's duration output to milliseconds for + # uniformity with existing metadata and consequently leaving these + # conversion methods in place for now. def milliseconds_to_formatted_time(milliseconds, include_fractions = true) total_seconds = milliseconds / 1000 hours = total_seconds / (60 * 60) diff --git a/app/helpers/media_objects_helper.rb b/app/helpers/media_objects_helper.rb index 04b0d60968..9ca5edd012 100644 --- a/app/helpers/media_objects_helper.rb +++ b/app/helpers/media_objects_helper.rb @@ -51,18 +51,6 @@ def dropbox_url collection ic.iconv(url) end - def combined_display_date media_object - (issued,created) = case media_object - when MediaObject, SpeedyAF::Proxy::MediaObject - [media_object.date_issued, media_object.date_created] - when Hash - [media_object[:document]['date_issued_ssi'], media_object[:document]['date_created_ssi']] - end - result = issued - result += " (Creation date: #{created})" if created.present? - result - end - def display_other_identifiers media_object # bibliographic_id has form [:type,"value"], other_identifier has form [[:type,"value],[:type,"value"],...] ids = media_object.bibliographic_id.present? ? [media_object.bibliographic_id] : [] diff --git a/app/javascript/components/MediaObjectRamp.jsx b/app/javascript/components/MediaObjectRamp.jsx index 250e4d3ef1..c876dd65d2 100644 --- a/app/javascript/components/MediaObjectRamp.jsx +++ b/app/javascript/components/MediaObjectRamp.jsx @@ -28,20 +28,9 @@ import "@samvera/ramp/dist/ramp.css"; import { Col, Row, Tab, Tabs } from 'react-bootstrap'; import './Ramp.scss'; -const ExpandCollapseArrow = () => { - return ( - - - - ); -}; - const Ramp = ({ urls, sections_count, - has_structure, title, share, timeline, @@ -53,10 +42,6 @@ const Ramp = ({ const [manifestUrl, setManifestUrl] = React.useState(''); const [startCanvasId, setStartCanvasId] = React.useState(); const [startCanvasTime, setStartCanvasTime] = React.useState(); - const [isClosed, setIsClosed] = React.useState(false); - - let expandCollapseBtnRef = React.useRef(); - let interval; React.useEffect(() => { const { base_url, fullpath_url } = urls; @@ -79,71 +64,15 @@ const Ramp = ({ : undefined ); setManifestUrl(url); - - // Attach player event listeners when there's structure - if (has_structure) { - interval = setInterval(addPlayerEventListeners, 500); - } - - // Clear interval upon component unmounting - return () => clearInterval(interval); }, []); - /** - * Listen to player's events to update the structure navigation - * UI - */ - const addPlayerEventListeners = () => { - let player = document.getElementById('iiif-media-player'); - if (player && player.player != undefined && !player.player.isDisposed()) { - let playerInst = player.player; - playerInst.on('loadedmetadata', () => { - playerInst.on('timeupdate', () => { - setIsClosed(false); - }); - }); - // Expand sections when a new Canvas is loaded into the player - playerInst.on('ready', () => { - setIsClosed(false); - }); - } - }; - - React.useEffect(() => { - expandCollapseSections(isClosed); - }, [isClosed]); - - const handleCollapseExpand = () => { - setIsClosed(isClosed => !isClosed); - }; - - const expandCollapseSections = (isClosing) => { - const allSections = $('div[class*="ramp--structured-nav__section"]'); - allSections.each(function (index, section) { - let sectionUl = section.nextSibling; - if (sectionUl) { - if (isClosing) { - sectionUl.classList.remove('expanded'); - sectionUl.classList.add('closed'); - expandCollapseBtnRef.current.classList.remove('expanded'); - expandCollapseBtnRef.current.classList.add('closed'); - } else { - sectionUl.classList.remove('closed'); - sectionUl.classList.add('expanded'); - expandCollapseBtnRef.current.classList.remove('closed'); - expandCollapseBtnRef.current.classList.add('expanded'); - } - } - }); - }; - return ( - + {(cdl.enabled && !cdl.can_stream) ? (
@@ -208,19 +137,6 @@ const Ramp = ({ } - {has_structure && - - - - }
@@ -237,14 +153,14 @@ const Ramp = ({ - +
} ) } - + {cdl.enabled &&
} diff --git a/app/javascript/components/PlaylistRamp.jsx b/app/javascript/components/PlaylistRamp.jsx index fb8e5927e9..01cbc69f9d 100644 --- a/app/javascript/components/PlaylistRamp.jsx +++ b/app/javascript/components/PlaylistRamp.jsx @@ -159,10 +159,10 @@ const Ramp = ({ emptyManifestMessage='This playlist currently has no playable items.' startCanvasId={startCanvasId}> - + {playlist_item_ids?.length > 0 && ( - +

{activeItemTitle}

{activeItemSummary &&
{activeItemSummary}
} @@ -194,7 +194,7 @@ const Ramp = ({
)} - + diff --git a/app/javascript/components/Ramp.scss b/app/javascript/components/Ramp.scss index 1956294053..ca93afc3dd 100644 --- a/app/javascript/components/Ramp.scss +++ b/app/javascript/components/Ramp.scss @@ -19,6 +19,10 @@ } .ramp--all-components { + .ramp--structured-nav.display>.ramp--structured-nav__sections { + align-items: center; + } + .ramp--media_player { .video-js .vjs-big-play-button { left: 55% !important; @@ -278,7 +282,8 @@ .ramp--structured-nav { margin-top: 0px; min-width: inherit; - max-height: inherit; + max-height: 100%; + overflow: hidden; } .tab-pane { @@ -323,6 +328,15 @@ } .ramp--playlist-accordion { + // Set accordion with to fit player's minimum width + min-width: 490px; + &.mobile-view { + min-width: 380px; + } + + border-radius: 0 0 0.25em 0.25em; + border-top: none; + .accordion .card { border: none; border-bottom: 1px solid rgba(0, 0, 0, 0.125); diff --git a/app/javascript/components/ReactButtonContainer.scss b/app/javascript/components/ReactButtonContainer.scss index 1dcd151890..1647ca63e4 100644 --- a/app/javascript/components/ReactButtonContainer.scss +++ b/app/javascript/components/ReactButtonContainer.scss @@ -23,7 +23,7 @@ } .modal-open .modal { - height: 100vh; + height: calc(100vh - 80px); overflow: scroll; } diff --git a/app/javascript/components/embeds/EmbeddedRamp.jsx b/app/javascript/components/embeds/EmbeddedRamp.jsx index 867bc07a96..102bad16a9 100644 --- a/app/javascript/components/embeds/EmbeddedRamp.jsx +++ b/app/javascript/components/embeds/EmbeddedRamp.jsx @@ -110,12 +110,14 @@ const Ramp = ({ }; return ( - - - +
+ + + +
); }; diff --git a/app/javascript/components/embeds/Ramp.scss b/app/javascript/components/embeds/Ramp.scss index 4a54fa8771..4f3c100eb8 100644 --- a/app/javascript/components/embeds/Ramp.scss +++ b/app/javascript/components/embeds/Ramp.scss @@ -18,18 +18,44 @@ font-family: Arial, Helvetica, sans-serif; } -.ramp--media_player { +// Scope CSS to only embedded player +.embedded-ramp .ramp--media_player { .video-js .vjs-big-play-button { left: 55% !important; } + .video-js.vjs-audio-only-mode { + // Player height to the height of iframe container in embeds + max-height: 40px !important; + // Disable tooltips for volume and progress in embedded // audio. Viewport is too small to display them. .vjs-volume-panel:hover .vjs-mouse-display, - .vjs-custom-progress-bar .tooltiptext { + .vjs-custom-progress-bar .vjs-time-tooltip { display: none !important; } + + // Display progress-bar inline with player controls + .vjs-custom-progress-bar { + position: relative; + top: 0.475em; + margin: 0 0.15em; + } + + .vjs-volume-horizontal .vjs-volume-level span.vjs-icon-placeholder { + margin-top: 0.15em; + } + + // Set control-bar to player's width and remove spacers & offset + .vjs-control-bar { + &::after, &::before { + content: none; + } + width: 100% !important; + left: 0; + } } + @media (max-width: 585px) { .video-js .vjs-big-play-button { scale: 1.5; diff --git a/app/jobs/bulk_action_jobs.rb b/app/jobs/bulk_action_jobs.rb index a1ca94efe3..6e068b999a 100644 --- a/app/jobs/bulk_action_jobs.rb +++ b/app/jobs/bulk_action_jobs.rb @@ -134,6 +134,9 @@ def perform(documents, _params) successes = [] documents.each do |id| media_object = MediaObject.find(id) + supplemental_files = media_object.supplemental_files + DeleteChildFiles.perform_now(supplemental_files, nil) + if media_object.destroy successes += [media_object] else @@ -144,6 +147,19 @@ def perform(documents, _params) end end + class DeleteChildFiles < ActiveJob::Base + def perform(documents, _params) + documents.each do |doc| + begin + doc.destroy + rescue + logger.error("Failed to delete supplemental file #{doc.id}") + next + end + end + end + end + class Move < ActiveJob::Base def perform(documents, params) collection = Admin::Collection.find( params[:target_collection_id] ) diff --git a/app/jobs/master_file_management_jobs.rb b/app/jobs/master_file_management_jobs.rb index 72c4c1a819..f35de08acd 100644 --- a/app/jobs/master_file_management_jobs.rb +++ b/app/jobs/master_file_management_jobs.rb @@ -81,14 +81,8 @@ def perform(id) when 'file' then File.delete(oldpath) when 's3' then FileLocator::S3File.new(locator.source).object.delete end - masterfile.file_location = "" - masterfile.save Rails.logger.info "#{oldpath} has been deleted" else - unless masterfile.file_location.empty? - masterfile.file_location = "" - masterfile.save - end Rails.logger.warn "MasterFile #{oldpath} does not exist" end end diff --git a/app/models/access_control_step.rb b/app/models/access_control_step.rb index 4a4ad6f582..6a888eb547 100644 --- a/app/models/access_control_step.rb +++ b/app/models/access_control_step.rb @@ -90,8 +90,8 @@ def execute context end if context['remove_lease'].present? limited_access_submit = true - lease = Lease.find( context['remove_lease'] ) - media_object.governing_policies.delete( lease ) + lease = Lease.find(context['remove_lease']) + media_object.governing_policies.delete(lease) lease.destroy end @@ -108,21 +108,6 @@ def execute context end end - media_object.save! - - #Setup these values in the context because the edit partial is being rendered without running the controller's #edit (VOV-2978) - media_object.reload - context[:users] = media_object.read_users - context[:groups] = media_object.read_groups - context[:virtual_groups] = media_object.virtual_read_groups - context[:ip_groups] = media_object.ip_read_groups - context[:group_leases] = media_object.leases('local') - context[:user_leases] = media_object.leases('user') - context[:virtual_leases] = media_object.leases('external') - context[:ip_leases] = media_object.leases('ip') - context[:addable_groups] = Admin::Group.non_system_groups.reject { |g| context[:groups].include? g.name } - context[:addable_courses] = Course.all.reject { |c| context[:virtual_groups].include? c.context_id } - context[:lending_period] = media_object.lending_period context end diff --git a/app/models/batch_entries.rb b/app/models/batch_entries.rb index 8b5ea83bf3..a62a02226e 100644 --- a/app/models/batch_entries.rb +++ b/app/models/batch_entries.rb @@ -27,7 +27,7 @@ class BatchEntries < ActiveRecord::Base def ensure_mininum_viable_metadata return nil if minimal_viable_metadata? self.error = true - self.error_message = 'To successfully ingest, either title and date issued must be set or a bibliographic id must be provided' + self.error_message = 'To successfully ingest, either title must be set or a bibliographic id must be provided' end def queue @@ -54,7 +54,7 @@ def minimal_viable_metadata? return false if payload.nil? # nil guard fields = JSON.parse(payload)['fields'] return false if fields.blank? - return false if (fields['date_issued'].blank? || fields['title'].blank?) && fields['bibliographic_id'].blank? + return false if fields['title'].blank? && fields['bibliographic_id'].blank? true end diff --git a/app/models/concerns/iiif_supplemental_file_behavior.rb b/app/models/concerns/iiif_supplemental_file_behavior.rb index c9a6e4c433..48dbabba6f 100644 --- a/app/models/concerns/iiif_supplemental_file_behavior.rb +++ b/app/models/concerns/iiif_supplemental_file_behavior.rb @@ -33,7 +33,7 @@ def supplemental_files_rendering(object) end def object_supplemental_file_url(object, supplemental_file) - if object.is_a? MasterFile + if object.is_a?(MasterFile) || object.is_a?(SpeedyAF::Proxy::MasterFile) Rails.application.routes.url_helpers.master_file_supplemental_file_url(id: supplemental_file.id, master_file_id: object.id) else Rails.application.routes.url_helpers.media_object_supplemental_file_url(id: supplemental_file.id, media_object_id: object.id) diff --git a/app/models/concerns/media_object_mods.rb b/app/models/concerns/media_object_mods.rb index 17261d173a..44bca2b5cd 100644 --- a/app/models/concerns/media_object_mods.rb +++ b/app/models/concerns/media_object_mods.rb @@ -488,6 +488,7 @@ def record_identifier=(value) # Put the pieces into the right order and validate to make sure that there are no # syntactic errors def normalize_desc_metadata! + return unless descMetadata.content_changed? descMetadata.ensure_identifier_exists!(self.uri) descMetadata.update_change_date! descMetadata.reorder_elements! @@ -495,6 +496,11 @@ def normalize_desc_metadata! end def delete_all_values(*field_name) + # Manually mark the content as changed when deleteing values. + # Adding values causes calls into active_fedora-datastreams which markes the content as changed. + # The ng_xml_will_change! call here covers the case when all values are deleted and none are added back. + # This also markes the content as changed. + descMetadata.ng_xml_will_change! descMetadata.find_by_terms(*field_name).each &:remove end end diff --git a/app/models/derivative.rb b/app/models/derivative.rb index a39d22b5b7..fdb078469b 100644 --- a/app/models/derivative.rb +++ b/app/models/derivative.rb @@ -106,8 +106,8 @@ def self.from_output(output, managed = true) derivative.managed = managed derivative.track_id = output[:id] derivative.duration = output[:duration].to_i - # FIXME: Implement this in ActiveEncode - # derivative.mime_type = output[:mime_type] + # FIXME: Implement this in ActiveEncode or determine mimetype here + derivative.mime_type = output[:mime_type].presence derivative.quality = output[:label].sub(/quality-/, '') derivative.audio_bitrate = output[:audio_bitrate] diff --git a/app/models/iiif_canvas_presenter.rb b/app/models/iiif_canvas_presenter.rb index 53688984bd..d0245cadf1 100644 --- a/app/models/iiif_canvas_presenter.rb +++ b/app/models/iiif_canvas_presenter.rb @@ -27,7 +27,7 @@ def initialize(master_file:, stream_info:, media_fragment: nil) delegate :derivative_ids, :id, to: :master_file def to_s - master_file.display_title + master_file.structure_title end def range @@ -98,21 +98,29 @@ def service def video_content # @see https://github.com/samvera-labs/iiif_manifest - stream_urls.collect { |quality, _url| video_display_content(quality) } + stream_urls.collect { |quality, url, mimetype| video_display_content(quality, url, mimetype) } end - def video_display_content(quality) - IIIFManifest::V3::DisplayContent.new(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality), - **manifest_attributes(quality, 'Video')) + def video_display_content(quality, url, mimetype) + if mimetype.present? && mimetype != 'application/x-mpegURL' + IIIFManifest::V3::DisplayContent.new(url, **manifest_attributes(quality, 'Video', mimetype: mimetype)) + else + IIIFManifest::V3::DisplayContent.new(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality), + **manifest_attributes(quality, 'Video')) + end end def audio_content - stream_urls.collect { |quality, _url| audio_display_content(quality) } + stream_urls.collect { |quality, url, mimetype| audio_display_content(quality, url, mimetype) } end - def audio_display_content(quality) - IIIFManifest::V3::DisplayContent.new(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality), - **manifest_attributes(quality, 'Sound')) + def audio_display_content(quality, url, mimetype) + if mimetype.present? && mimetype != 'application/x-mpegURL' + IIIFManifest::V3::DisplayContent.new(url, **manifest_attributes(quality, 'Sound', mimetype: mimetype)) + else + IIIFManifest::V3::DisplayContent.new(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality), + **manifest_attributes(quality, 'Sound')) + end end def supplementing_content_data(file) @@ -142,7 +150,7 @@ def supplementing_content_data(file) def stream_urls stream_info[:stream_hls].collect do |d| - [d[:quality], d[:url]] + [d[:quality], d[:url], d[:mimetype]] end end @@ -214,14 +222,14 @@ def parse_hour_min_sec(s) smh[0] + (60 * smh[1]) + (3600 * smh[2]) end - def manifest_attributes(quality, media_type) + def manifest_attributes(quality, media_type, mimetype: 'application/x-mpegURL') media_hash = { label: quality, width: (master_file.width || '1280').to_i, height: (master_file.height || MasterFile::AUDIO_HEIGHT).to_i, duration: stream_info[:duration], type: media_type, - format: 'application/x-mpegURL' + format: mimetype }.compact if master_file.media_object.visibility == 'public' diff --git a/app/models/iiif_manifest_presenter.rb b/app/models/iiif_manifest_presenter.rb index 4dab4f39b2..113a9b517a 100644 --- a/app/models/iiif_manifest_presenter.rb +++ b/app/models/iiif_manifest_presenter.rb @@ -45,7 +45,7 @@ def description end def to_s - media_object.title + media_object.title || media_object.id end def manifest_metadata @@ -100,6 +100,7 @@ def metadata_field(label, value, default = nil) end def combined_display_date(media_object) + #FIXME Does this need to change now that date_issued is not required and thus could be nil result = media_object.date_issued result += " (Creation date: #{media_object.date_created})" if media_object.date_created.present? result @@ -173,6 +174,13 @@ def series_url(series) Rails.application.routes.url_helpers.blacklight_url({ "f[collection_ssim][]" => media_object.collection.name, "f[series_ssim][]" => series }) end + def display_search_linked(solr_field, values) + Array(values).collect do |value| + url = Rails.application.routes.url_helpers.blacklight_url({ "f[#{solr_field}][]" => value }) + "#{value}" + end + end + def display_lending_period(media_object) return nil unless lending_enabled ActiveSupport::Duration.build(media_object.lending_period).to_day_hour_s @@ -180,14 +188,15 @@ def display_lending_period(media_object) def iiif_metadata_fields fields = [ - metadata_field('Title', media_object.title), - metadata_field('Date', combined_display_date(media_object), 'Not provided'), + metadata_field('Title', media_object.title, media_object.id), + metadata_field('Publication date', media_object.date_issued), + metadata_field('Creation date', media_object.date_created), metadata_field('Main contributor', media_object.creator), metadata_field('Summary', display_summary(media_object)), metadata_field('Contributor', media_object.contributor), metadata_field('Publisher', media_object.publisher), metadata_field('Genre', media_object.genre), - metadata_field('Subject', media_object.topical_subject), + metadata_field('Subject', display_search_linked("subject_ssim", media_object.topical_subject)), metadata_field('Time period', media_object.temporal_subject), metadata_field('Location', media_object.geographic_subject), metadata_field('Collection', display_collection(media_object)), diff --git a/app/models/iiif_playlist_canvas_presenter.rb b/app/models/iiif_playlist_canvas_presenter.rb index 887c0793fe..64b2038dad 100644 --- a/app/models/iiif_playlist_canvas_presenter.rb +++ b/app/models/iiif_playlist_canvas_presenter.rb @@ -115,21 +115,29 @@ def metadata_field(label, value, default = nil) def video_content # @see https://github.com/samvera-labs/iiif_manifest - stream_urls.collect { |quality, _url| video_display_content(quality) } + stream_urls.collect { |quality, url, mimetype| video_display_content(quality, url, mimetype) } end - def video_display_content(quality) - IIIFManifest::V3::DisplayContent.new(CGI.unescape(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality, anchor: fragment_identifier)), - **manifest_attributes(quality, 'Video')) + def video_display_content(quality, url, mimetype) + if mimetype.present? && mimetype != 'application/x-mpegURL' + IIIFManifest::V3::DisplayContent.new(URI.join(url, "##{fragment_identifier}").to_s, **manifest_attributes(quality, 'Video', mimetype: mimetype)) + else + IIIFManifest::V3::DisplayContent.new(CGI.unescape(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality, anchor: fragment_identifier)), + **manifest_attributes(quality, 'Video')) + end end def audio_content - stream_urls.collect { |quality, _url| audio_display_content(quality) } + stream_urls.collect { |quality, url, mimetype| audio_display_content(quality, url, mimetype) } end - def audio_display_content(quality) - IIIFManifest::V3::DisplayContent.new(CGI.unescape(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality, anchor: fragment_identifier)), - **manifest_attributes(quality, 'Sound')) + def audio_display_content(quality, url, mimetype) + if mimetype.present? && mimetype != 'application/x-mpegURL' + IIIFManifest::V3::DisplayContent.new(URI.join(url, "##{fragment_identifier}").to_s, **manifest_attributes(quality, 'Sound', mimetype: mimetype)) + else + IIIFManifest::V3::DisplayContent.new(CGI.unescape(Rails.application.routes.url_helpers.hls_manifest_master_file_url(master_file.id, quality: quality, anchor: fragment_identifier)), + **manifest_attributes(quality, 'Sound')) + end end def marker_content(marker) @@ -174,7 +182,7 @@ def supplemental_attributes(file) def stream_urls stream_info[:stream_hls].collect do |d| - [d[:quality], d[:url]] + [d[:quality], d[:url], d[:mimetype]] end end @@ -192,14 +200,14 @@ def simple_iiif_range(label = stream_info[:embed_title]) ) end - def manifest_attributes(quality, media_type) + def manifest_attributes(quality, media_type, mimetype: 'application/x-mpegURL') media_hash = { label: quality, width: (master_file.width || '1280').to_i, height: (master_file.height || MasterFile::AUDIO_HEIGHT).to_i, duration: stream_info[:duration], type: media_type, - format: 'application/x-mpegURL' + format: mimetype }.compact if master_file.media_object.visibility == 'public' diff --git a/app/models/master_file.rb b/app/models/master_file.rb index 789471d64c..51e8706eae 100644 --- a/app/models/master_file.rb +++ b/app/models/master_file.rb @@ -15,6 +15,7 @@ require 'fileutils' require 'hooks' require 'open-uri' +require 'avalon/ffprobe' require 'avalon/file_resolver' require 'avalon/m3u8_reader' @@ -164,6 +165,7 @@ def error after_save :update_stills_from_offset!, if: Proc.new { |mf| mf.previous_changes.include?("poster_offset") || mf.previous_changes.include?("thumbnail_offset") } before_destroy :stop_processing! before_destroy :update_parent! + before_destroy :remove_child_files define_hooks :after_transcoding, :after_processing after_update_index { |mf| mf.media_object&.enqueue_long_indexing } @@ -562,12 +564,14 @@ def stop_processing! ActiveEncodeJobs::CancelEncodeJob.perform_later(workflow_id, id) if workflow_id.present? && !finished_processing? end - protected - - def mediainfo - Mediainfo.new(FileLocator.new(file_location).location, headers: @auth_header) + # Delete does not trigger callbacks so override method to ensure deletion of child supplemental files + def delete + remove_child_files + super end + protected + def find_frame_source(options={}) options[:offset] ||= 2000 @@ -589,7 +593,7 @@ def find_frame_source(options={}) end def create_frame_source_hls_temp_file(offset) - playlist_url = self.stream_details[:stream_hls].find { |d| d[:quality] == 'high' }[:url] + playlist_url = self.hls_streams.find { |d| d[:quality] == 'high' || d[:quality] == 'medium' || d[:quality] == 'low' }[:url] secure_url = SecurityHandler.secure_url(playlist_url, target: self.id) playlist = Avalon::M3U8Reader.read(secure_url) details = playlist.at(offset) @@ -722,33 +726,33 @@ def saveDerivativesHash(derivative_hash) end def reloadTechnicalMetadata! - #Reset mediainfo - @mediainfo = mediainfo + # Reset ffprobe + @ffprobe = Avalon::FFprobe.new(FileLocator.new(file_location)) # Formats like MP4 can be caught as both audio and video # so the case statement flows in the preferred order - self.file_format = if @mediainfo.video? + self.file_format = if @ffprobe.video? 'Moving image' - elsif @mediainfo.audio? + elsif @ffprobe.audio? 'Sound' else 'Unknown' end self.duration = begin - @mediainfo.duration.to_s + @ffprobe.duration.to_s rescue nil end - unless @mediainfo.video.streams.empty? - display_aspect_ratio_s = @mediainfo.video.streams.first.display_aspect_ratio + unless !@ffprobe.video? + display_aspect_ratio_s = @ffprobe.display_aspect_ratio if ':'.in? display_aspect_ratio_s self.display_aspect_ratio = display_aspect_ratio_s.split(/:/).collect(&:to_f).reduce(:/).to_s else self.display_aspect_ratio = display_aspect_ratio_s end - self.original_frame_size = @mediainfo.video.streams.first.frame_size + self.original_frame_size = @ffprobe.original_frame_size end end @@ -792,6 +796,10 @@ def update_parent! end end + def remove_child_files + BulkActionJobs::DeleteChildFiles.perform_later(supplemental_files, nil) + end + private def generate_waveform diff --git a/app/models/media_object.rb b/app/models/media_object.rb index c1eb6aa305..e9e7085248 100644 --- a/app/models/media_object.rb +++ b/app/models/media_object.rb @@ -79,7 +79,6 @@ class MediaObject < ActiveFedora::Base # validates :governing_policies, presence: true if Proc.new { |mo| mo.changes["governing_policy_ids"].empty? } validates :title, presence: true, if: :resource_description_active? - validates :date_issued, presence: true, if: :resource_description_active? validate :validate_language, if: :resource_description_active? validate :validate_related_items, if: :resource_description_active? validate :validate_dates, if: :resource_description_active? @@ -182,6 +181,10 @@ def section_ids return [] if self.section_list.nil? @section_ids = JSON.parse(self.section_list) end + + def published? + !avalon_publisher.blank? + end def destroy # attempt to stop the matterhorn processing job @@ -301,6 +304,13 @@ def fill_in_solr_fields_that_need_sections(solr_doc) solr_doc['all_comments_ssim'] = all_comments end + def fill_in_solr_fields_needing_leases(solr_doc) + solr_doc['read_access_virtual_group_ssim'] = virtual_read_groups + leases('external').map(&:inherited_read_groups).flatten + solr_doc['read_access_ip_group_ssim'] = collect_ips_for_index(ip_read_groups + leases('ip').map(&:inherited_read_groups).flatten) + solr_doc[Hydra.config.permissions.read.group] ||= [] + solr_doc[Hydra.config.permissions.read.group] += solr_doc['read_access_ip_group_ssim'] + end + # Enqueue background job to do a full indexing including more costly fields that read from children def enqueue_long_indexing MediaObjectIndexingJob.perform_later(id) @@ -311,10 +321,6 @@ def to_solr(include_child_fields: false) solr_doc[ActiveFedora.index_field_mapper.solr_name("workflow_published", :facetable, type: :string)] = published? ? 'Published' : 'Unpublished' solr_doc[ActiveFedora.index_field_mapper.solr_name("collection", :symbol, type: :string)] = collection.name if collection.present? solr_doc[ActiveFedora.index_field_mapper.solr_name("unit", :symbol, type: :string)] = collection.unit if collection.present? - solr_doc['read_access_virtual_group_ssim'] = virtual_read_groups + leases('external').map(&:inherited_read_groups).flatten - solr_doc['read_access_ip_group_ssim'] = collect_ips_for_index(ip_read_groups + leases('ip').map(&:inherited_read_groups).flatten) - solr_doc[Hydra.config.permissions.read.group] ||= [] - solr_doc[Hydra.config.permissions.read.group] += solr_doc['read_access_ip_group_ssim'] solr_doc["title_ssort"] = self.title solr_doc["creator_ssort"] = Array(self.creator).join(', ') solr_doc["date_ingested_ssim"] = self.create_date.strftime "%F" if self.create_date.present? @@ -327,9 +333,10 @@ def to_solr(include_child_fields: false) solr_doc['section_id_ssim'] = section_ids if include_child_fields fill_in_solr_fields_that_need_sections(solr_doc) + fill_in_solr_fields_needing_leases(solr_doc) elsif id.present? # avoid error in test suite # Fill in other identifier so these values aren't stripped from the solr doc while waiting for the background job - mf_docs = ActiveFedora::SolrService.query("isPartOf_ssim:#{id}", rows: 1_000_000) + mf_docs = ActiveFedora::SolrService.query("isPartOf_ssim:#{id}", rows: 100_000) solr_doc["other_identifier_sim"] += mf_docs.collect { |h| h['identifier_ssim'] }.flatten end @@ -470,7 +477,7 @@ def section_solr_docs # in the section_list return [] unless section_ids.present? query = "id:" + section_ids.join(" id:") - @section_docs ||= ActiveFedora::SolrService.query(query, rows: 1_000_000) + @section_docs ||= ActiveFedora::SolrService.query(query, rows: 100_000) end def calculate_duration diff --git a/app/models/resource_description_step.rb b/app/models/resource_description_step.rb index d357f11562..57010c6091 100644 --- a/app/models/resource_description_step.rb +++ b/app/models/resource_description_step.rb @@ -24,9 +24,8 @@ def execute context media_object.descMetadata.populate_from_catalog!(context[:media_object_params][:bibliographic_id][:id],context[:media_object_params][:bibliographic_id][:source]) else media_object.permalink = context[:media_object_params].delete(:permalink) - media_object.update_attributes(context[:media_object_params]) + media_object.assign_attributes(context[:media_object_params]) end - media_object.save context end end diff --git a/app/models/search_builder.rb b/app/models/search_builder.rb index 6b02d50579..891d2d13fa 100644 --- a/app/models/search_builder.rb +++ b/app/models/search_builder.rb @@ -65,7 +65,8 @@ def term_frequency_counts(solr_parameters) transcripts_present = SupplementalFile.with_tag('transcript').any? # List of fields for displaying on search results (Blacklight index fields) - fl = ['id', 'has_model_ssim', 'title_tesi', 'date_issued_ssi', 'creator_ssim', 'abstract_ssi', 'duration_ssi', 'section_id_ssim', 'avalon_resource_type_ssim'] + fl = ['id', 'has_model_ssim', 'title_tesi', 'date_issued_ssi', 'creator_ssim', 'abstract_ssi', 'duration_ssi', 'section_id_ssim', 'avalon_resource_type_ssim', + 'descMetadata_modified_dtsi', 'timestamp'] # Add a field for matching child sections fl << "sections:[subquery]" diff --git a/app/models/structure_step.rb b/app/models/structure_step.rb index aa35b0935f..2475a75817 100644 --- a/app/models/structure_step.rb +++ b/app/models/structure_step.rb @@ -19,9 +19,8 @@ def initialize(step = 'structure', title = "Structure", summary = "Organization def execute context media_object = context[:media_object] - if ! context[:master_file_ids].nil? + if context[:master_file_ids].present? media_object.section_ids = context[:master_file_ids] - media_object.save end context end diff --git a/app/models/supplemental_file.rb b/app/models/supplemental_file.rb index e000c73eb8..a324532f74 100644 --- a/app/models/supplemental_file.rb +++ b/app/models/supplemental_file.rb @@ -25,7 +25,7 @@ class SupplementalFile < ApplicationRecord validates :parent_id, presence: true validate :validate_file_type, if: :caption? - serialize :tags, Array + serialize :tags, type: Array # Need to prepend so this runs before the callback added by `has_one_attached` above # See https://github.com/rails/rails/issues/37304 diff --git a/app/presenters/admin/collection_presenter.rb b/app/presenters/admin/collection_presenter.rb index c4b07064bb..7bc4fec13b 100644 --- a/app/presenters/admin/collection_presenter.rb +++ b/app/presenters/admin/collection_presenter.rb @@ -44,15 +44,15 @@ def unpublished_media_object_count end def managers - @managers ||= document["edit_access_person_ssim"] & (document["collection_managers_ssim"] || []) + @managers ||= Array(document["edit_access_person_ssim"]) & Array(document["collection_managers_ssim"]) end def editors - @editors ||= document["edit_access_person_ssim"] - managers + @editors ||= Array(document["edit_access_person_ssim"]) - managers end def depositors - document["read_access_person_ssim"] + Array(document["read_access_person_ssim"]) end def manager_count diff --git a/app/presenters/encode_presenter.rb b/app/presenters/encode_presenter.rb index 96b85b9953..37b78b8220 100644 --- a/app/presenters/encode_presenter.rb +++ b/app/presenters/encode_presenter.rb @@ -29,7 +29,7 @@ def initialize(encode_record) delegate :id, :adapter, :display_title, :master_file_id, :media_object_id, :created_at, :progress, to: :encode_record def status - @encode_record.state.capitalize + @encode_record.state&.capitalize end def global_id diff --git a/app/services/file_locator.rb b/app/services/file_locator.rb index 6b6740c079..65041c88c3 100644 --- a/app/services/file_locator.rb +++ b/app/services/file_locator.rb @@ -118,10 +118,19 @@ def reader when 'file' File.open(location,'r') else - Kernel::open(uri.to_s, 'r') + open_uri end end + # Ruby 3.0 removed URI#open from being called by Kernel#open. + # Prioritize using URI#open, attempt to fallback to Kernel#open + # if URI fails. + def open_uri + URI.open(uri, 'r') + rescue + Kernel::open(uri.to_s, 'r') + end + def attachment case uri.scheme when 's3' diff --git a/app/services/waveform_service.rb b/app/services/waveform_service.rb index fc0ce6f93b..4100443a87 100644 --- a/app/services/waveform_service.rb +++ b/app/services/waveform_service.rb @@ -75,33 +75,37 @@ def empty_waveform(master_file) def get_normalized_peaks(uri) wave_io = get_wave_io(uri.to_s) - peaks = gather_peaks(wave_io) + peaks = [] + reader = nil + abs_max = 0 + begin + reader = WaveFile::Reader.new(wave_io) + reader.each_buffer(@samples_per_pixel) do |buffer| + sample_min, sample_max = buffer.samples.minmax + peaks << [sample_min, sample_max] + abs_max = [abs_max, sample_min.abs, sample_max.abs].max + end + rescue WaveFile::InvalidFormatError + # ffmpeg generated no wavefile data + end return [] if peaks.blank? - max_peak = peaks.flatten.map(&:abs).max res = 2**(@bit_res - 1) - factor = max_peak.zero? ? 1 : res / max_peak.to_f - peaks.map { |peak| peak.collect { |num| (num * factor).to_i } } + factor = abs_max.zero? ? 1 : res / abs_max.to_f + peaks.each do |peak| + peak[0] = (peak[0] * factor).to_i + peak[1] = (peak[1] * factor).to_i + end + peaks ensure - Process.wait(wave_io.pid) if wave_io&.pid + reader&.close + wave_io&.close end def get_wave_io(uri) headers = "-headers $'Referer: #{Rails.application.routes.url_helpers.root_url}\r\n'" if uri.starts_with? "http" normalized_uri = uri.starts_with?("file") ? Addressable::URI.unencode(uri) : uri timeout = 60000000 # Must be in microseconds. Current value = 1 minute. - cmd = "#{Settings.ffmpeg.path} #{headers} -rw_timeout #{timeout} -i '#{normalized_uri}' -f wav -ar 44100 - 2> /dev/null" + cmd = "#{Settings.ffmpeg.path} #{headers} -rw_timeout #{timeout} -i '#{normalized_uri}' -f wav -ar 44100 -ac 1 - 2> /dev/null" IO.popen(cmd) end - - def gather_peaks(wav_file) - peaks = [] - begin - WaveFile::Reader.new(wav_file).each_buffer(@samples_per_pixel) do |buffer| - peaks << [buffer.samples.flatten.min, buffer.samples.flatten.max] - end - rescue WaveFile::InvalidFormatError - # ffmpeg generated no wavefile data - end - peaks - end end diff --git a/app/views/bookmarks/update_access_control.html.erb b/app/views/bookmarks/update_access_control.html.erb index e63eb8e4b8..06f16a4d42 100644 --- a/app/views/bookmarks/update_access_control.html.erb +++ b/app/views/bookmarks/update_access_control.html.erb @@ -74,51 +74,61 @@ Unless required by applicable law or agreed to in writing, software distributed Special Access <%= label_tag "user" do %> <%= render partial: "modules/tooltip", locals: { form: form, field: 'user', tooltip: t("access_control.user"), options: {display_label: t("access_control.userlabel").html_safe} } %> -
+
<%= hidden_field_tag "user" %> <%= text_field_tag "user_display", nil, class: "typeahead from-model form-control", - data: { model: 'user', target: "user" } %>
- <%= text_field_tag "add_user_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
+ data: { model: 'user', target: "user" } %> + <%= text_field_tag "add_user_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> <%= text_field_tag "add_user_end", nil, placeholder: 'End Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
- <%= button_tag "Add", name: 'submit_add_user', class:'btn btn-outline', value: 'Add' %> - <%= button_tag "Remove", name: 'submit_remove_user', class:'btn btn-outline remove_access', value: 'Remove' %> +
+ <%= button_tag "Add", name: 'submit_add_user', class:'btn btn-outline', value: 'Add' %> + <%= button_tag "Remove", name: 'submit_remove_user', class:'btn btn-outline remove_access', value: 'Remove' %> +
<% end %> <%= label_tag "group" do %> <%= render partial: "modules/tooltip", locals: { form: form, field: 'group', tooltip: t("access_control.group"), options: {display_label: t("access_control.grouplabel").html_safe} } %> - <% dropdown_values = [Admin::Group.non_system_groups, 'name', 'name'] %> - <% dropdown_values = options_from_collection_for_select(*dropdown_values) %> - <%= select_tag "group", - dropdown_values, - { include_blank: true, class: "form-control"}%>
- <%= text_field_tag "add_group_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
- <%= text_field_tag "add_group_end", nil, placeholder: 'End Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> - <%= button_tag "Add", name: 'submit_add_group', class:'btn btn-outline', value: 'Add' %> - <%= button_tag "Remove", name: 'submit_remove_group', class:'btn btn-outline remove_access', value: 'Remove' %> +
+ <% dropdown_values = [Admin::Group.non_system_groups, 'name', 'name'] %> + <% dropdown_values = options_from_collection_for_select(*dropdown_values) %> + <%= select_tag "group", + dropdown_values, + { include_blank: true, class: "form-control"}%> + <%= text_field_tag "add_group_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> + <%= text_field_tag "add_group_end", nil, placeholder: 'End Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> +
+
+ <%= button_tag "Add", name: 'submit_add_group', class:'btn btn-outline', value: 'Add' %> + <%= button_tag "Remove", name: 'submit_remove_group', class:'btn btn-outline remove_access', value: 'Remove' %> +
<% end %> <%= label_tag "class" do %> <%= render partial: "modules/tooltip", locals: { form: form, field: 'class', tooltip: t("access_control.class"), options: {display_label: t("access_control.classlabel").html_safe} } %> -
+
<%= hidden_field_tag "class" %> <%= text_field_tag "class_display", nil, class: "typeahead from-model form-control", - data: { model: 'externalGroup', target: "class" } %>
- <%= text_field_tag "add_class_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
+ data: { model: 'externalGroup', target: "class" } %> + <%= text_field_tag "add_class_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> <%= text_field_tag "add_class_end", nil, placeholder: 'End Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
- <%= button_tag "Add", name: 'submit_add_class', class:'btn btn-outline', value: 'Add' %> - <%= button_tag "Remove", name: 'submit_remove_class', class:'btn btn-outline remove_access', value: 'Remove' %> +
+ <%= button_tag "Add", name: 'submit_add_class', class:'btn btn-outline', value: 'Add' %> + <%= button_tag "Remove", name: 'submit_remove_class', class:'btn btn-outline remove_access', value: 'Remove' %> +
<% end %> <%= label_tag "ipaddress" do %> <%= render partial: "modules/tooltip", locals: { form: form, field: 'ipaddress', tooltip: t("access_control.ipaddress"), options: {display_label: t("access_control.ipaddresslabel").html_safe} } %> -
- <%= text_field_tag "ipaddress", nil, class: "form-control" %>
- <%= text_field_tag "add_ipaddress_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
+
+ <%= text_field_tag "ipaddress", nil, class: "form-control" %> + <%= text_field_tag "add_ipaddress_begin", nil, placeholder: 'Begin Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %> <%= text_field_tag "add_ipaddress_end", nil, placeholder: 'End Date (yyyy-mm-dd)', class: 'form-control date-input access_date', autocomplete: 'off' %>
- <%= button_tag "Add", name: 'submit_add_ipaddress', class:'btn btn-outline', value: 'Add' %> - <%= button_tag "Remove", name: 'submit_remove_ipaddress', class:'btn btn-outline remove_access', value: 'Remove' %> +
+ <%= button_tag "Add", name: 'submit_add_ipaddress', class:'btn btn-outline', value: 'Add' %> + <%= button_tag "Remove", name: 'submit_remove_ipaddress', class:'btn btn-outline remove_access', value: 'Remove' %> +
<% end %>