diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index 7ae73decc..d75d0d8c7 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -3232,8 +3232,31 @@ public void addNavTrail(NavTree root) } } + /** + * Reading library spectra from a large library file can take many seconds over network storage, especially for large + * EncyclopeDIA libraries. To protect public folders from aggressive bots, do not show library spectra to guests when + * the library is large. Show the login prompt instead. + * Returns true (and adds the login view) when the library spectrum should be withheld. + */ + private boolean addGuestSpectrumGate(TargetedMSRun run, VBox vbox) + { + if (!LibrarySpectrumMatchGetter.blockSpectraForGuest(getUser(), run.getId())) + { + return false; + } + HtmlView loginView = getLoginView(getViewContext(), getContainer()); + loginView.setTitle("Library Spectrum"); + loginView.setFrame(WebPartView.FrameType.PORTAL); + vbox.addView(loginView); + return true; + } + private void addSpectrumViews(TargetedMSRun run, VBox vbox, Precursor precursor, BindException errors) { + if (addGuestSpectrumGate(run, vbox)) + { + return; + } PipeRoot root = PipelineService.get().getPipelineRootSetting(getContainer()); if (null != root) { @@ -3252,6 +3275,10 @@ private void addSpectrumViews(TargetedMSRun run, VBox vbox, Precursor precursor, private void addSpectrumViews(TargetedMSRun run, VBox vbox, Peptide peptide, BindException errors) { + if (addGuestSpectrumGate(run, vbox)) + { + return; + } PipeRoot root = PipelineService.get().getPipelineRootSetting(getContainer()); if (null != root) { @@ -3336,6 +3363,13 @@ public Object execute(SpectrumDataForm form, BindException errors) } TargetedMSRun run = TargetedMSManager.getRunForGeneralMolecule(peptide.getId()); + // Apply the same guest gate as the spectrum views (see LibrarySpectrumMatchGetter.blockSpectraForGuest). + if (LibrarySpectrumMatchGetter.blockSpectraForGuest(getUser(), run.getId())) + { + response.put("error", "Login to view this data"); + return response; + } + List libraries = LibraryManager.getLibraries(run.getId()); PeptideSettings.SpectrumLibrary library = null; for (PeptideSettings.SpectrumLibrary lib : libraries) diff --git a/src/org/labkey/targetedms/chart/ChromatogramDataset.java b/src/org/labkey/targetedms/chart/ChromatogramDataset.java index afeba8579..c78fd36cc 100644 --- a/src/org/labkey/targetedms/chart/ChromatogramDataset.java +++ b/src/org/labkey/targetedms/chart/ChromatogramDataset.java @@ -901,6 +901,13 @@ public void build() protected List getPeptideIdRetentionTimes() { + // Skip peptide-ID retention-time markers for guests when the library is large. Reading large libraries can be slow over network storage. + // See LibrarySpectrumMatchGetter.blockSpectraForGuest. + if (LibrarySpectrumMatchGetter.blockSpectraForGuest(_user, _run.getId())) + { + return Collections.emptyList(); + } + SampleFile sampleFile = ReplicateManager.getSampleFile(_pChromInfo.getSampleFileId()); // TODO: May want to move LocalDirectory up to controller, where others are created. Sharing probably desired. diff --git a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java index 531e9f161..cd5e4178a 100644 --- a/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java +++ b/src/org/labkey/targetedms/view/spectrum/LibrarySpectrumMatchGetter.java @@ -16,6 +16,7 @@ package org.labkey.targetedms.view.spectrum; import org.apache.commons.io.FilenameUtils; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.cache.BlockingCache; @@ -24,6 +25,7 @@ import org.labkey.api.data.Container; import org.labkey.api.security.User; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.logging.LogHelper; import org.labkey.targetedms.TargetedMSManager; import org.labkey.targetedms.TargetedMSRun; import org.labkey.targetedms.TargetedMSSchema; @@ -42,6 +44,8 @@ import org.labkey.targetedms.query.PeptideManager; import org.labkey.targetedms.query.PrecursorManager; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.sql.SQLException; import java.util.ArrayList; @@ -59,8 +63,64 @@ */ public class LibrarySpectrumMatchGetter { + private static final Logger LOG = LogHelper.getLogger(LibrarySpectrumMatchGetter.class, "Matches library spectra and retention times for the library spectrum viewer"); + private static final int CACHE_SIZE = 10; + // Reading library spectra and retention times from large spectrum libraries can be slow over network storage. + // For EncyclopeDIA .elib we read one row per source file for the peptide. This can be hundreds of rows and the needed + // columns are not in the index, so each table row lookup is a separate network round-trip on GPFS. + // For BiblioSpec .blib we scan the unindexed RetentionTimes table for the RT of the peptide in all the scans and source + // files. + // PanoramaWeb has large files of both types, so the size gate covers both library types. To protect public folders from + // aggressive bots, library spectra are not shown to guests when the library file is at or above this size. Guests are + // asked to log in instead. + private static final long GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT = 500L * 1024 * 1024; // 500 MB + + /** + * Returns true if library spectra should NOT be shown to the given user for the given run, + * i.e. the user is a guest and the run references a supported spectrum library file that is at + * or above {@link #GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT}. Logged-in users are never blocked, and + * small libraries are read in place as before. + */ + public static boolean blockSpectraForGuest(User user, long runId) + { + if (!user.isGuest()) + { + return false; + } + for (Path libPath : LibraryManager.getLibraryFilePaths(runId).values()) + { + if (isLargeSpectrumLibrary(libPath)) + { + return true; + } + } + return false; + } + + private static boolean isLargeSpectrumLibrary(Path libPath) + { + // Only .elib/.blib libraries are read for spectra; ignore anything we cannot read. + if (libPath == null || getReaderForLibrary(FileUtil.getFileName(libPath)) == null) + { + return false; + } + try + { + // Files.size throws NoSuchFileException if the file is missing, so a separate Files.exists + // check is unnecessary and would add a second filesystem round-trip on network storage. + return Files.size(libPath) >= GUEST_SPECTRUM_LIBRARY_SIZE_LIMIT; + } + catch (IOException e) + { + // If we cannot stat the file it is missing or unreadable, in which case the + // downstream library read will fail too. + LOG.warn("Could not determine size of spectrum library file " + libPath, e); + return false; + } + } + private static final BlockingCache> _peptideIdRtsCache = CacheManager.getBlockingCache(CACHE_SIZE, CacheManager.DAY, "TargetedMS peptide ID retention times", (precursor, argument) -> {