diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 9db7bbfca..b0f328a67 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -23,6 +23,6 @@ jobs: - uses: actions/checkout@v2 - name: openapi-lint - uses: mhiew/redoc-lint-github-action@v2 + uses: mbowman100/swagger-validator-action@master with: - args: 'public/swagger.yml --skip-rule operation-operationId' + files: public/swagger.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b23d0aae..0cf7f16a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 1.20.2 - 2022-04-30 + +### Fixed +- swagger lint action +- When downloading a file with a `'` in the name it would save the file as blob +- Fix for a rare race condition with masonry where tiles could end up overlapping in space page. +- Fixes bug where same extractor shows up multiple times and all Clowder instances index db on reindex [#327](https://github.com/clowder-framework/clowder/issues/327) + +### Changed +- Changed `Enabled By SuperAdmin` to read `Enabled by Server Admin` [#344](https://github.com/clowder-framework/clowder/issues/344) + ## 1.20.1 - 2022-04-04 ### Fixed diff --git a/app/services/mongodb/MongoDBQueueService.scala b/app/services/mongodb/MongoDBQueueService.scala index b52030465..6fab9539f 100644 --- a/app/services/mongodb/MongoDBQueueService.scala +++ b/app/services/mongodb/MongoDBQueueService.scala @@ -93,7 +93,8 @@ trait MongoDBQueueService { // start pool to being processing queue actions def listen() = { - if (queueTimer == null) { + //only if this is the primary clowder instance + if (queueTimer == null && configuration.getBoolean("clowder.primary").getOrElse(true)) { // TODO: Need to make these in a separate pool queueTimer = Akka.system().scheduler.schedule(0 seconds, 5 millis) { getNextQueuedAction match { diff --git a/app/services/rabbitmq/RabbitMQMessageService.scala b/app/services/rabbitmq/RabbitMQMessageService.scala index a5d5699a6..47ed9da6b 100644 --- a/app/services/rabbitmq/RabbitMQMessageService.scala +++ b/app/services/rabbitmq/RabbitMQMessageService.scala @@ -128,24 +128,27 @@ class RabbitMQMessageService extends MessageService { new MsgConsumer(channel.get, event_filter.get) ) - // Start actor to listen to extractor heartbeats - Logger.info("Starting extractor heartbeat listener") - // create fanout exchange if it doesn't already exist - channel.get.exchangeDeclare("extractors", "fanout", true) - // anonymous queue - val heartbeatsQueue = channel.get.queueDeclare().getQueue - // bind queue to exchange - channel.get.queueBind(heartbeatsQueue, "extractors", "*") - extractorsHeartbeats = Some(Akka.system.actorOf( - Props(new ExtractorsHeartbeats(channel.get, heartbeatsQueue)), name = "ExtractorsHeartbeats" - )) - Logger.debug("Initializing a MsgConsumer for the ExtractorsHeartbeats") - channel.get.basicConsume( - heartbeatsQueue, - false, // do not auto ack - "ExtractorsHeartbeats", // tagging the consumer is important if you want to stop it later - new MsgConsumer(channel.get, extractorsHeartbeats.get) - ) + //register new extractor only if this is the primary clowder instance + if (configuration.getBoolean("clowder.primary").getOrElse(true)) { + // Start actor to listen to extractor heartbeats + Logger.info("Starting extractor heartbeat listener") + // create fanout exchange if it doesn't already exist + channel.get.exchangeDeclare("extractors", "fanout", true) + // anonymous queue + val heartbeatsQueue = channel.get.queueDeclare().getQueue + // bind queue to exchange + channel.get.queueBind(heartbeatsQueue, "extractors", "*") + extractorsHeartbeats = Some(Akka.system.actorOf( + Props(new ExtractorsHeartbeats(channel.get, heartbeatsQueue)), name = "ExtractorsHeartbeats" + )) + Logger.debug("Initializing a MsgConsumer for the ExtractorsHeartbeats") + channel.get.basicConsume( + heartbeatsQueue, + false, // do not auto ack + "ExtractorsHeartbeats", // tagging the consumer is important if you want to stop it later + new MsgConsumer(channel.get, extractorsHeartbeats.get) + ) + } // Setup Actor to submit new extractions to broker extractQueue = Some(Akka.system.actorOf(Props(new PublishDirectActor(channel = channel.get, diff --git a/app/util/FileUtils.scala b/app/util/FileUtils.scala index 1d66d2f93..fd5790f1d 100644 --- a/app/util/FileUtils.scala +++ b/app/util/FileUtils.scala @@ -856,37 +856,20 @@ object FileUtils { //Download CONTENT-DISPOSITION encoding // def encodeAttachment(filename: String, userAgent: String) : String = { - val filenameStar = if (userAgent.indexOf("MSIE") > -1) { - URLEncoder.encode(filename, "UTF-8") - } else if (userAgent.indexOf("Edge") > -1){ - MimeUtility.encodeText(filename - .replaceAll(",","%2C") - .replaceAll("\"","%22") - .replaceAll("/","%2F") - .replaceAll("=","%3D") - .replaceAll("&","%26") - .replaceAll(":","%3A") - .replaceAll(";","%3B") - .replaceAll("\\?","%3F") - .replaceAll("\\*","%2A") + val filenameStar = MimeUtility.encodeText(filename + .replaceAll("%","%25") + .replaceAll(" ","%20") + .replaceAll("\"","%22") + .replaceAll("'","%27") + .replaceAll(",","%2C") + .replaceAll("/","%2F") + .replaceAll("=","%3D") + .replaceAll(":","%3A") + .replaceAll(";","%3B") + .replaceAll("\\*","%2A") ,"utf-8","Q") - } else { - MimeUtility.encodeText(filename - .replaceAll("%","%25") - .replaceAll(" ","%20") - .replaceAll("\"","%22") - .replaceAll(",","%2C") - .replaceAll("/","%2F") - .replaceAll("=","%3D") - .replaceAll(":","%3A") - .replaceAll(";","%3B") - .replaceAll("\\*","%2A") - ,"utf-8","Q") - } - Logger.debug(userAgent + ": " + filenameStar) //Return the complete attachment header info "attachment; filename*=UTF-8''" + filenameStar } - } diff --git a/app/views/spaces/space.scala.html b/app/views/spaces/space.scala.html index 696b81112..363b67a3a 100644 --- a/app/views/spaces/space.scala.html +++ b/app/views/spaces/space.scala.html @@ -148,7 +148,7 @@

@Messages("collections.title")

match.masonry(); // layout Masonry again after all images have loaded - imagesLoaded( '.tiled-image', function() { + imagesLoaded( match, function() { match.masonry({ itemSelector: '.tiled-image', columnWidth: '.post-box', diff --git a/app/views/spaces/updateExtractors.scala.html b/app/views/spaces/updateExtractors.scala.html index 0e53789a7..6b4147435 100644 --- a/app/views/spaces/updateExtractors.scala.html +++ b/app/views/spaces/updateExtractors.scala.html @@ -35,8 +35,8 @@

Space Extractors

Name Description Process Triggers - Enabled By SuperAdmin - Use Default + Enabled by Server Admin + Use Default Enable in Space Disable in Space diff --git a/conf/application.conf b/conf/application.conf index c05870d3d..2aad1f5df 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -183,6 +183,13 @@ api.version="beta" # mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]] mongodbURI = "mongodb://127.0.0.1:27017/clowder" +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Clowder Primary Instance +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# This is required if there are multiple clowder instances. This config variable indicates +# the primary clowder instance who takes care of special actions like registering new extractors, +# indexing db on a reindex etc. The default value is true which means only one instance of clowder running. +clowder.primary=true # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # RabbitMQ diff --git a/doc/src/sphinx/conf.py b/doc/src/sphinx/conf.py index c103902ec..cf120a58b 100644 --- a/doc/src/sphinx/conf.py +++ b/doc/src/sphinx/conf.py @@ -22,7 +22,7 @@ author = 'Luigi Marini' # The full version, including alpha/beta/rc tags -release = '1.20.1' +release = '1.20.2' # -- General configuration --------------------------------------------------- diff --git a/docker/custom.conf b/docker/custom.conf index be917b637..29fedbe9c 100644 --- a/docker/custom.conf +++ b/docker/custom.conf @@ -37,6 +37,10 @@ smtp.host=${?SMTP_SERVER} service.byteStorage=services.filesystem.DiskByteStorageService service.byteStorage=${?CLOWDER_STORAGE} +#primary Clowder instance +clowder.primary=true +clowder.primary=${?CLOWDER_PRIMARY} + # location in case of services.filesystem.DiskByteStorageService clowder.diskStorage.path="/home/clowder/data" clowder.diskStorage.path=${?CLOWDER_DISKPATH} diff --git a/project/Build.scala b/project/Build.scala index e12fa7db2..196e7df4f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -13,7 +13,7 @@ import NativePackagerKeys._ object ApplicationBuild extends Build { val appName = "clowder" - val version = "1.20.1" + val version = "1.20.2" val jvm = "1.7" def appVersion: String = { diff --git a/public/swagger.yml b/public/swagger.yml index 78ade0465..548169eae 100644 --- a/public/swagger.yml +++ b/public/swagger.yml @@ -9,7 +9,7 @@ info: Clowder is a customizable and scalable data management system to support any data format and multiple research domains. It is under active development and deployed for a variety of research projects. - version: 1.20.1 + version: 1.20.2 termsOfService: https://clowder.ncsa.illinois.edu/clowder/tos contact: name: Clowder