diff --git a/app/controllers/public/resources_controller.rb b/app/controllers/public/resources_controller.rb index 461abb0..70761e3 100644 --- a/app/controllers/public/resources_controller.rb +++ b/app/controllers/public/resources_controller.rb @@ -6,7 +6,7 @@ class Public::ResourcesController < Api::ResourcesController prepend_before_action :set_project_id, only: :index prepend_before_action :set_resource_id, only: [:show, :destroy, :update] prepend_before_action :set_resource_project_id, only: [:create, :update] - skip_before_action :authenticate_request, only: [:content, :download, :inline, :manifest, :preview, :thumbnail] + skip_before_action :authenticate_request, only: [:content, :download, :iiif, :inline, :manifest, :preview, :thumbnail] def content redirect_resource :content_url @@ -41,7 +41,12 @@ def thumbnail def redirect_resource(attribute) resource = Resource.find_by_uuid(params[:id]) - redirect_to resource.send(attribute), allow_other_host: true + render status: :not_found and return if resource.nil? + + redirect = resource.send(attribute) + render status: :not_found and return if redirect.nil? + + redirect_to redirect, allow_other_host: true end def set_project_id diff --git a/app/jobs/convert_image_job.rb b/app/jobs/convert_image_job.rb index 311df9a..24e2cfa 100644 --- a/app/jobs/convert_image_job.rb +++ b/app/jobs/convert_image_job.rb @@ -21,8 +21,10 @@ def perform(resource_id) content_type: 'image/tiff' ) - rescue MiniMagick::Error + rescue MiniMagick::Error => e # Content cannot be converted + Rails.logger.error e.message + Rails.logger.error e.backtrace.join("\n") end end end diff --git a/app/models/concerns/attachable.rb b/app/models/concerns/attachable.rb index 4bf9e61..41cdb04 100644 --- a/app/models/concerns/attachable.rb +++ b/app/models/concerns/attachable.rb @@ -103,7 +103,7 @@ def generate_url_method(name) return nil if attachment.audio? - "#{self.send("#{name}_base_url")}/full/^500,/0/default.jpg" + "#{self.send("#{name}_base_url")}/full/^!500,500/0/default.jpg" end define_method("#{name}_thumbnail_url") do @@ -112,7 +112,7 @@ def generate_url_method(name) return nil if attachment.audio? - "#{self.send("#{name}_base_url")}/square/!250,250/0/default.jpg" + "#{self.send("#{name}_base_url")}/square/^!250,250/0/default.jpg" end end diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 3e130d6..daabfa7 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -9,6 +9,7 @@ allow do origins '*' resource '/public/resources/:id/manifest', headers: :any, methods: :get + resource '/public/resources/:id/content', headers: :any, methods: :get resource '*/rails/active_storage/blobs/redirect/*', headers: :any, methods: :get end end diff --git a/db/migrate/20240612152139_enable_pgcrypto.rb b/db/migrate/20240612152139_enable_pgcrypto.rb new file mode 100644 index 0000000..9573ea3 --- /dev/null +++ b/db/migrate/20240612152139_enable_pgcrypto.rb @@ -0,0 +1,5 @@ +class EnablePgcrypto < ActiveRecord::Migration[7.0] + def change + enable_extension 'pgcrypto' + end +end diff --git a/db/migrate/20240612152246_set_resource_uuid_default_value.rb b/db/migrate/20240612152246_set_resource_uuid_default_value.rb new file mode 100644 index 0000000..c55be83 --- /dev/null +++ b/db/migrate/20240612152246_set_resource_uuid_default_value.rb @@ -0,0 +1,5 @@ +class SetResourceUuidDefaultValue < ActiveRecord::Migration[7.0] + def change + change_column :resources, :uuid, :uuid, default: 'gen_random_uuid()', using: 'uuid::uuid' + end +end diff --git a/db/schema.rb b/db/schema.rb index 5c05a06..92f6dc8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_10_25_202507) do +ActiveRecord::Schema[7.0].define(version: 2024_06_12_152246) do # These are extensions that must be enabled in order to support this database + enable_extension "pgcrypto" enable_extension "plpgsql" create_table "active_storage_attachments", force: :cascade do |t| @@ -94,7 +95,7 @@ t.text "exif" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "uuid" + t.uuid "uuid", default: -> { "gen_random_uuid()" } t.text "manifest" t.jsonb "user_defined", default: {} t.index ["project_id"], name: "index_resources_on_project_id" diff --git a/lib/iiif/README.md b/lib/iiif/README.md new file mode 100644 index 0000000..e0b19fd --- /dev/null +++ b/lib/iiif/README.md @@ -0,0 +1,76 @@ +# Cantaloupe IIIF +Instructions for setting up a Cantaloupe IIIF instance + +## Requirements +- Cantaloupe 5.0 +- Java 11+ +- FFMPEG + +## Install Dependencies + +```bash +sudo apt install default-jre unzip wget ffmpeg -y +``` + +## Download Cantaloupe + +```bash +sudo wget https://github.com/cantaloupe-project/cantaloupe/releases/download/v5.0.6/cantaloupe-5.0.6.zip +``` + +## Custom Delegate +Add the contents of `custom_delegate.rb` to: + +```bash +/root/cantaloupe-5.0.6/delegates.rb +``` + +## Modify cantaloupe.properties (Optional) +Set custom properties in `/root/cantaloupe-5.0.6/cantalouple.properties` + +### Enable Admin Console + +```bash +endpoint.admin.enabled = true +endpoint.admin.username = admin +endpoint.admin.secret = +``` + +### Enable Delegate Script + +```bash +delegate_script.enabled = true +``` + +## Create a service +Cantaloupe run as a Java application. To set up Cantaloupe to run as a service on Ubuntu, add the `cantaloupe.service` file to: + +``` +/etc/systemd/system/cantaloupe.service +``` + +## Create start.sh +Add the `start.sh` file to `/root`. Change the Cantaloupe version if necessary. + +## Enable/start the service + +```bash +sudo systemctl daemon-reload +sudo systemctl enable cantaloupe.service +sudo systemctl start cantaloupe +sudo systemctl status cantaloupe +``` + +## Logging + +### Setup + +```bash +sudo journalctl --unit=cantaloupe +``` + +### View + +```bash +sudo journalctl -f -u cantaloupe +``` \ No newline at end of file diff --git a/lib/iiif/cantaloupe.service b/lib/iiif/cantaloupe.service new file mode 100644 index 0000000..2287b27 --- /dev/null +++ b/lib/iiif/cantaloupe.service @@ -0,0 +1,20 @@ +[Unit] +Description=Cantaloupe IIIF Image Server Service + +[Service] +User=root + +# The configuration file application.properties should be here: + +# Change this to your workspace +WorkingDirectory=/root + +# Path to executable. Executable is a bash script which calls jar file +ExecStart=/bin/bash -c "/root/start.sh" + +# Restart the service on failure +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/lib/iiif/start.sh b/lib/iiif/start.sh new file mode 100755 index 0000000..4719c5e --- /dev/null +++ b/lib/iiif/start.sh @@ -0,0 +1 @@ +java -Dcantaloupe.config=/root/cantaloupe-5.0.5/cantaloupe.properties -Xmx6g -jar /root/cantaloupe-5.0.5/cantaloupe-5.0.5.jar \ No newline at end of file diff --git a/lib/tasks/iiif.rake b/lib/tasks/iiif.rake index 720b2f7..7c52fd4 100644 --- a/lib/tasks/iiif.rake +++ b/lib/tasks/iiif.rake @@ -1,5 +1,32 @@ namespace :iiif do + desc 'Converts the source images to pyramidal TIFFs for all resources' + task convert_images: :environment do + Resource.all.in_batches do |resources| + resources.pluck(:id).each do |resource_id| + ConvertImageJob.perform_now(resource_id) + end + end + end + + desc 'Converts the source images to pyramidal TIFFs for resources with no converted content' + task convert_images_empty: :environment do + query = Resource + .where.not( + ActiveStorage::Attachment + .where(ActiveStorage::Attachment.arel_table[:record_id].eq(Resource.arel_table[:id])) + .where(record_type: Resource.to_s, name: 'content_converted') + .arel + .exists + ) + + query.in_batches do |resources| + resources.pluck(:id).each do |resource_id| + ConvertImageJob.perform_now(resource_id) + end + end + end + desc 'Creates a new IIIF manifest for all resources' task create_manifests: :environment do Resource.all.in_batches do |resources| @@ -9,6 +36,33 @@ namespace :iiif do end end + desc 'Creates a new IIIF manifest for resources with no generated manifest' + task create_manifests_empty: :environment do + Resource.where(manifest: nil).in_batches do |resources| + resources.pluck(:id).each do |resource_id| + CreateManifestJob.perform_now(resource_id) + end + end + end + + desc 'Extracts the EXIF data from all resources' + task extract_exif: :environment do + Resource.all.in_batches do |resources| + resources.pluck(:id).each do |resource_id| + ExtractExifJob.perform_now(resource_id) + end + end + end + + desc 'Extracts the EXIF data from resources with no EXIF data' + task extract_exif_empty: :environment do + Resource.where(exif: nil).in_batches do |resources| + resources.pluck(:id).each do |resource_id| + ExtractExifJob.perform_now(resource_id) + end + end + end + desc 'Transfers resources from one storage service to another' task transfer_resources: :environment do # Parse the arguments