diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/query/ObsCorePackager.java b/src/firefly/java/edu/caltech/ipac/firefly/server/query/ObsCorePackager.java index 88e7847dad..15e08d9a24 100644 --- a/src/firefly/java/edu/caltech/ipac/firefly/server/query/ObsCorePackager.java +++ b/src/firefly/java/edu/caltech/ipac/firefly/server/query/ObsCorePackager.java @@ -1,6 +1,9 @@ package edu.caltech.ipac.firefly.server.query; -import edu.caltech.ipac.firefly.data.*; +import edu.caltech.ipac.firefly.data.DownloadRequest; +import edu.caltech.ipac.firefly.data.FileInfo; +import edu.caltech.ipac.firefly.data.HttpResultInfo; +import edu.caltech.ipac.firefly.data.ServerRequest; import edu.caltech.ipac.firefly.server.ServerContext; import edu.caltech.ipac.firefly.server.db.EmbeddedDbUtil; import edu.caltech.ipac.firefly.server.network.HttpServiceInput; @@ -9,12 +12,17 @@ import edu.caltech.ipac.table.DataGroup; import edu.caltech.ipac.table.MappedData; import edu.caltech.ipac.table.io.VoTableReader; +import edu.caltech.ipac.util.FileUtil; import edu.caltech.ipac.util.download.URLDownload; import java.io.File; import java.io.IOException; -import java.util.*; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static edu.caltech.ipac.util.FileUtil.getUniqueFileNameForGroup; import static org.apache.commons.lang.StringUtils.substringAfterLast; @@ -55,27 +63,32 @@ private List computeFileGroup(DownloadRequest request) throws DataAcc String contentType = tmpFileInfo.getAttribute(HttpResultInfo.CONTENT_TYPE); String colNames = request.getSearchRequest().getParam("templateColNames"); + boolean useSourceUrlFileName= request.getSearchRequest().getBooleanParam("useSourceUrlFileName",false); String[] cols = colNames != null ? colNames.split(",") : null; - String ext_file_name = getFileName(idx,cols,dgDataUrl); + String ext_file_name = getFileName(idx,cols,useSourceUrlFileName,dgDataUrl,url); String extension; if (access_format != null) { if (access_format.contains("datalink")) { ext_file_name = getUniqueFileNameForGroup(ext_file_name, dataLinkFolders); - List tmpFileInfos = parseDatalink(url, ext_file_name); + List tmpFileInfos = parseDatalink(url, ext_file_name, useSourceUrlFileName); fileInfos.addAll(tmpFileInfos); } else { //non datalink entry - such as fits,jpg etc. - extension = access_format.replaceAll(".*/", ""); - ext_file_name += "." + extension; + if (!useSourceUrlFileName || !FileUtil.getExtension(ext_file_name).isEmpty()) { + extension = access_format.replaceAll(".*/", ""); + ext_file_name += "." + extension; + } FileInfo fileInfo = new FileInfo(tmpFile.getPath(), ext_file_name, 0); fileInfos.add(fileInfo); } } else { //access_format is null, so try and get it from the url's Content_Type - extension = getExtFromURL(contentType); - ext_file_name += "." + extension; + if (!useSourceUrlFileName || !FileUtil.getExtension(ext_file_name).isEmpty()) { + extension = getExtFromURL(contentType); + ext_file_name += "." + extension; + } FileInfo fileInfo = new FileInfo(tmpFile.getPath(), ext_file_name, 0); fileInfos.add(fileInfo); } @@ -89,7 +102,7 @@ private List computeFileGroup(DownloadRequest request) throws DataAcc return Arrays.asList(new FileGroup(fileInfos, null, 0, "ObsCore Download Files")); } - public static List parseDatalink(URL url, String prepend_file_name) { + public static List parseDatalink(URL url, String prepend_file_name, boolean useSourceUrlFileName) { List fileInfos = new ArrayList<>();; try { File tmpFile = File.createTempFile("datlink-", ".xml", ServerContext.getTempWorkDir()); @@ -125,13 +138,26 @@ public static List parseDatalink(URL url, String prepend_file_name) { if (testSem(sem,"#this") || testSem(sem,"#thumbnail") ||testSem(sem,"#preview") || testSem(sem,"#preview-plot")) { - String ext_file_name = prepend_file_name; - ext_file_name = testSem(sem, "#this") ? ext_file_name : ext_file_name + "-" + substringAfterLast(sem, "#"); - String extension = "unknown"; - extension = content_type != null ? getExtFromURL(content_type) : getExtFromURL(accessUrl); - ext_file_name += "." + extension; - //create a folder only if makeDataLinkFolder is true - ext_file_name = makeDataLinkFolder ? prepend_file_name + "/" + ext_file_name : ext_file_name; + + String ext_file_name= null; + if (useSourceUrlFileName) { + String fileName= new URL(accessUrl).getFile(); + String ext= FileUtil.getExtension(fileName); + if (!ext.isEmpty()) ext_file_name= fileName; + } + if (ext_file_name==null){ + ext_file_name = prepend_file_name; + ext_file_name = testSem(sem, "#this") ? ext_file_name : ext_file_name + "-" + substringAfterLast(sem, "#"); + String extension = "unknown"; + extension = content_type != null ? getExtFromURL(content_type) : getExtFromURL(accessUrl); + ext_file_name += "." + extension; + //create a folder only if makeDataLinkFolder is true + ext_file_name = makeDataLinkFolder ? prepend_file_name + "/" + ext_file_name : ext_file_name; + } + + + + FileInfo fileInfo = new FileInfo(accessUrl, ext_file_name, 0); fileInfos.add(fileInfo); } @@ -170,13 +196,20 @@ public static String makeValidString(String val) { //remove spaces and replace all non-(alphanumeric,period,underscore,hyphen) chars with underscore return val.replaceAll("\\s", "").replaceAll("[^a-zA-Z0-9._-]", "_"); } - public static String getFileName(int idx, String[] colNames, MappedData dgDataUrl) { + public static String getFileName(int idx, String[] colNames, boolean useSourceUrlFileName, MappedData dgDataUrl, URL url) { String obs_collection = (String) dgDataUrl.get(idx, "obs_collection"); String instrument_name = (String) dgDataUrl.get(idx, "instrument_name"); String obs_id = (String) dgDataUrl.get(idx, "obs_id"); String obs_title = (String) dgDataUrl.get(idx, "obs_title"); String file_name = ""; + //option 0: if useSourceUrlFileName is true the try to get it from the URL + if (useSourceUrlFileName) { + String fileName= url.getFile(); + String ext= FileUtil.getExtension(fileName); + if (!ext.isEmpty()) return fileName; + } + //option 1: use productTitleTemplate (colNames) if available to construct file name if (colNames != null) { for(String col : colNames) { diff --git a/src/firefly/js/templates/common/ttFeatureWatchers.js b/src/firefly/js/templates/common/ttFeatureWatchers.js index 9e666cf9d1..26ad04533b 100644 --- a/src/firefly/js/templates/common/ttFeatureWatchers.js +++ b/src/firefly/js/templates/common/ttFeatureWatchers.js @@ -5,7 +5,7 @@ import {dispatchAddTableTypeWatcherDef} from '../../core/MasterSaga.js'; import {dispatchTableUiUpdate} from '../../tables/TablesCntlr.js'; import {getActiveTableId, getMetaEntry, getTableUiByTblId, getTblById} from '../../tables/TableUtil.js'; import {DownloadButton, DownloadOptionPanel} from '../../ui/DownloadDialog.jsx'; -import {getTapObsCoreOptions} from '../../ui/tap/TableSearchHelpers.jsx'; +import {getTapObsCoreOptions, getTapObsCoreOptionsGuess} from '../../ui/tap/TableSearchHelpers.jsx'; import {isObsCoreLike} from '../../voAnalyzer/TableAnalysis.js'; import {getCatalogWatcherDef} from '../../visualize/saga/CatalogWatcher.js'; import {getUrlLinkWatcherDef} from '../../visualize/saga/UrlLinkWatcher.js'; @@ -66,11 +66,17 @@ function setupObsCorePackaging(tbl_id) { } function updateSearchRequest( tbl_id='', dlParams='', sRequest=null) { - const template= getAppOptions().tapObsCore?.productTitleTemplate; + const hostname= new URL(sRequest.source)?.hostname; + + const template= getTapObsCoreOptionsGuess(hostname)?.productTitleTemplate; + const useSourceUrlFileName= getTapObsCoreOptionsGuess(hostname)?.packagerUsesSourceUrlFileName; + + const templateColNames= template && getColNameFromTemplate(template); const searchRequest = cloneDeep( sRequest); searchRequest.template = template; searchRequest.templateColNames = templateColNames?.toString(); + searchRequest.useSourceUrlFileName= useSourceUrlFileName; return searchRequest; } @@ -85,7 +91,7 @@ const PrepareDownload = React.memo(() => {
getAppOptions().tapObsCore?.[serviceLabel] ?? getAppOptions().tapObsCore ?? {}; + + +export function getTapObsCoreOptionsGuess(serviceLabelGuess) { + const {tapObsCore={}}= getAppOptions(); + if (!serviceLabelGuess) return tapObsCore; + const guessKey= Object.entries(tapObsCore) + .find( ([key,value]) => isObject(value) && serviceLabelGuess.includes(key))?.[0]; + return getTapObsCoreOptions(guessKey); +} + + /** * make a FieldErrorList object * @returns {FieldErrorList} diff --git a/src/firefly/js/ui/tap/TapSearchRootPanel.jsx b/src/firefly/js/ui/tap/TapSearchRootPanel.jsx index c24ed1c880..928739a317 100644 --- a/src/firefly/js/ui/tap/TapSearchRootPanel.jsx +++ b/src/firefly/js/ui/tap/TapSearchRootPanel.jsx @@ -17,6 +17,7 @@ import {dispatchTableSearch} from 'firefly/tables/TablesCntlr.js'; import {showInfoPopup, showYesNoPopup} from 'firefly/ui/PopupUtil.jsx'; import {dispatchHideDialog} from 'firefly/core/ComponentCntlr.js'; import {dispatchHideDropDown} from 'firefly/core/LayoutCntlr.js'; +import {MetaConst} from '../../data/MetaConst.js'; import { ConstraintContext, getTapUploadSchemaEntry, getUploadServerFile, getUploadTableName, getHelperConstraints, getUploadConstraint, @@ -32,6 +33,7 @@ import { import {SectionTitle, AdqlUI, BasicUI} from 'firefly/ui/tap/TableSelectViewPanel.jsx'; import {useFieldGroupMetaState} from '../SimpleComponent.jsx'; import {PREF_KEY} from 'firefly/tables/TablePref.js'; +import {getTapObsCoreOptions} from './TableSearchHelpers.jsx'; @@ -98,6 +100,10 @@ export function getServiceLabel(serviceUrl) { return (serviceUrl && (tapOps.find( (e) => e.value===serviceUrl)?.labelOnly)) || ''; } +export function getServiceHiPS(serviceUrl) { + const tapOps= getTapServices(); + return (serviceUrl && (tapOps.find( (e) => e.value===serviceUrl)?.hipsUrl)) || ''; +} export function TapSearchPanel({initArgs= {}, titleOn=true}) { @@ -384,6 +390,7 @@ function onTapSearchSubmit(request,serviceUrl,tapBrowserState) { const hasMaxrec = !isNaN(parseInt(maxrec)); const doSubmit = () => { const serviceLabel= getServiceLabel(serviceUrl); + const hips= getServiceHiPS(serviceUrl); const adqlClean = adql.replace(/\s/g, ' '); // replace all whitespaces with spaces const params = {serviceUrl, QUERY: adqlClean}; if (isUpload) { @@ -399,6 +406,8 @@ function onTapSearchSubmit(request,serviceUrl,tapBrowserState) { additionalMeta[PREF_KEY]= `${tapBrowserState.schemaName}-${tapBrowserState.tableName}`; } additionalMeta.serviceLabel= serviceLabel; + if (hips) additionalMeta[MetaConst.COVERAGE_HIPS]= hips; + treq.META_INFO= {...treq.META_INFO, ...additionalMeta }; dispatchTableSearch(treq, {backgroundable: true, showFilters: true, showInfoButton: true}); }; diff --git a/src/firefly/js/visualize/ZoomUtil.js b/src/firefly/js/visualize/ZoomUtil.js index f0457b7bf6..00054d1bee 100644 --- a/src/firefly/js/visualize/ZoomUtil.js +++ b/src/firefly/js/visualize/ZoomUtil.js @@ -21,8 +21,28 @@ import {convertAngle} from './VisUtil.js'; */ export const FullType = new Enum(['ONLY_WIDTH', 'WIDTH_HEIGHT', 'ONLY_HEIGHT']); -export const imageLevels= [.0125, .025, .05,.1,.25,.3, .4, .5, .75, .8, .9, 1, 1.25, 1.5,2, 2.5,3, 3.5, - 4,5, 6, 7,8, 9, 10, 11, 12, 13, 14, 15, 16, 18,20,22,24,28, 32]; +/** + * This is the master list of zoom levels for Firefly image displays. + * These zoom levels are used when clicking on the +/- icons, and are + * also used to construct the zoom-level dialog. Scroll-wheel zooming + * can reach levels between the steps. + */ + +export const imageLevels = [ + /* For zoomed-out images we use large steps - factors of 2. */ + 0.015625, 0.03125, 0.0625, 0.125, 0.25, + /* Otherwise we use four steps per factor of two, */ + /* i.e., the 4th root of 2 or about 1.189 / 19%, */ + /* rounded to three digits after the decimal. */ + 0.297, 0.354, 0.420, 0.5, + 0.595, 0.707, 0.841, 1, + 1.189, 1.414, 1.682, 2, + 2.378, 2.828, 3.364, 4, + 4.757, 5.657, 6.727, 8, + 9.514, 11.314, 13.454, 16, + 19.027, 22.627, 26.909, 32 +]; + export const imageLevelsReversed= [...imageLevels].reverse(); diff --git a/src/firefly/js/visualize/reducer/HandlePlotCreation.js b/src/firefly/js/visualize/reducer/HandlePlotCreation.js index a23f69bdcc..a84b068fb2 100644 --- a/src/firefly/js/visualize/reducer/HandlePlotCreation.js +++ b/src/firefly/js/visualize/reducer/HandlePlotCreation.js @@ -327,12 +327,11 @@ function plotOverlayFail(state,action) { } function plotFail(state,action) { - const {description, plotId}= action.payload; - const {plotViewAry}= state; - const plotView= getPlotViewById(state,plotId); - if (!plotView) return state; + const {description, plotId, plotIdAry:inPlotIdAry=[]}= action.payload; + const plotIdAry= plotId ? [plotId] : inPlotIdAry; const changes= {plottingStatusMsg:description,serverCall:'fail', nonRecoverableFail:true }; - return {...state, plotViewAry:clonePvAry(plotViewAry,plotId,changes)}; + const plotViewAry= state.plotViewAry?.map( (pv) => plotIdAry.includes(pv.plotId) ? {...pv,...changes} : pv); + return {...state, plotViewAry}; } function hipsFail(state,action) { diff --git a/src/firefly/js/visualize/task/PlotImageTask.js b/src/firefly/js/visualize/task/PlotImageTask.js index 09195a9dbf..e84abf46e1 100644 --- a/src/firefly/js/visualize/task/PlotImageTask.js +++ b/src/firefly/js/visualize/task/PlotImageTask.js @@ -88,7 +88,8 @@ async function executeGroupSearch(rawAction, dispatcher) { const wpResult= await callGetWebPlotGroup(wpRequestAry, requestKey); processPlotImageSuccessResponse(dispatcher,rawAction.payload,wpResult); } catch (e) { - dispatcher( { type: ImagePlotCntlr.PLOT_IMAGE_FAIL, payload: {wpRequestAry, error:e} } ); + const plotIdAry= wpRequestAry.map( (r) => r.getPlotId()).filter( (id) => id); + dispatcher( { type: ImagePlotCntlr.PLOT_IMAGE_FAIL, payload: {plotIdAry, wpRequestAry, error:e} } ); logger.error('plot group error', e); } }