Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6c4687a
move instead of copy
labkey-martyp Mar 22, 2023
090fddb
Move instead of copy file
labkey-martyp Apr 4, 2023
629ecae
module file
labkey-martyp Apr 4, 2023
8468009
Startup and file event handling
labkey-martyp Apr 5, 2023
1faa26f
new path
labkey-martyp Apr 7, 2023
349c7bd
Merge remote-tracking branch 'origin/release23.3-SNAPSHOT' into 23.3_…
labkey-martyp Apr 14, 2023
86c25f3
Handle re-copy and test symlink tracking
labkey-martyp Apr 19, 2023
de5c7ff
test fix
labkey-martyp Apr 25, 2023
2b11992
User target file root instead of temp directory
labkey-martyp Apr 26, 2023
5ff943b
Don't track symlinks in memory
labkey-martyp Apr 30, 2023
792b0ce
exp.Data cleanup
labkey-martyp May 1, 2023
d6ec455
Added test that deletes / renames / adds files before resubmitting
vagisha May 1, 2023
4e65577
Move files only on import and cleanup exp.data urls
labkey-martyp May 5, 2023
e80b861
Handle run
labkey-martyp May 5, 2023
7f7a291
special handling for clib file
labkey-martyp May 8, 2023
279299c
Handle deleting a copied container and cleanup
labkey-martyp May 9, 2023
c939cb5
Running on windows and NPE fix
labkey-martyp May 9, 2023
674c8e7
comment
labkey-martyp May 9, 2023
47fc128
Handle some error cases
labkey-martyp May 10, 2023
52cd232
- PanoramaPublicFileImporter logs to the job log, and throws an excep…
vagisha May 12, 2023
e26ee64
- Fire symlink update events only when file / container being moved /…
vagisha May 17, 2023
69d067d
Rework datafile alignment
labkey-martyp May 17, 2023
0c9671a
Scope datafile url to correct container
labkey-martyp May 17, 2023
3c65bf4
Removed PanoramaPublicFileWriter.
vagisha May 18, 2023
613d6a1
Limit the number of containers to look at when updating symlinks. Thi…
vagisha May 20, 2023
5d7ab66
Remove code to lookup runs in the source container when aligning data…
vagisha May 25, 2023
2bb5e8a
Function rename
labkey-martyp May 25, 2023
c3f835a
Final task symlink validation
labkey-martyp May 30, 2023
1e5d009
Added PanoramaPublicSymlinkTest
vagisha May 31, 2023
16c81ca
Code review
labkey-martyp May 31, 2023
e8503a3
Update panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicLis…
labkey-martyp May 31, 2023
5e8d310
remove error. causing issues with other imports
labkey-martyp Jun 6, 2023
1b9c638
Automation test : Verifying the uploaded files and symlinks in source…
labkey-sweta Jun 7, 2023
9f03b79
Merge remote-tracking branch 'origin/release23.3-SNAPSHOT' into 23.3_…
labkey-martyp Jun 19, 2023
aaa67fb
Merge branch '23.3_fb_copy_vs_mv_pano_files' of https://github.com/La…
labkey-martyp Jun 19, 2023
a1cbc20
Fix issue with export cleanup
labkey-martyp Jun 21, 2023
020c224
Add checkbox to disable move and symlink behavior
labkey-martyp Jun 22, 2023
0521674
Automated test and fix file copy
labkey-martyp Jun 25, 2023
21eb7cb
Turn off debug flag
labkey-martyp Jun 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,20 @@ private boolean validateAction(CopyExperimentForm form, BindException errors)
return true;
}

private Path getExportFilesDir(Container c)
{
FileContentService fcs = FileContentService.get();
if(fcs != null)
{
Path fileRoot = fcs.getFileRootPath(c, FileContentService.ContentType.files);
if (fileRoot != null)
{
return fileRoot.resolve(PipelineService.EXPORT_DIR);
}
}
return null;
}

@Override
public boolean handlePost(CopyExperimentForm form, BindException errors)
{
Expand Down Expand Up @@ -1539,13 +1553,15 @@ public boolean handlePost(CopyExperimentForm form, BindException errors)
return false;
}

String previousVersionName = null;
Submission previousSubmission = _journalSubmission.getLatestCopiedSubmission();
if (previousSubmission != null)
{
// Target folder name is automatically populated in the copy experiment form. Unless the admin making the copy changed the
// folder name we expect the previous copy of the data to have the same folder name. Rename the old folder so that we can
// use the same folder name for the new copy.
if (!renamePreviousFolder(previousSubmission, destinationFolder, errors))
previousVersionName = renamePreviousFolder(previousSubmission, destinationFolder, errors);
if (previousVersionName == null)
{
return false;
}
Expand Down Expand Up @@ -1573,8 +1589,13 @@ public boolean handlePost(CopyExperimentForm form, BindException errors)
job.setUsePxTestDb(form.isUsePxTestDb());
job.setAssignDoi(form.isAssignDoi());
job.setUseDataCiteTestApi(form.isUseDataCiteTestApi());
job.setMoveAndSymlink(form.isMoveAndSymlink());
job.setReviewerEmailPrefix(form.getReviewerEmailPrefix());
job.setDeletePreviousCopy(form.isDeleteOldCopy());
job.setPreviousVersionName(previousVersionName);
job.setExportTargetPath(getExportFilesDir(target));
job.setExportSourceContainer(form.getContainer());

PipelineService.get().queueJob(job);

_successURL = PageFlowUtil.urlProvider(PipelineStatusUrls.class).urlBegin(target);
Expand All @@ -1586,12 +1607,14 @@ public boolean handlePost(CopyExperimentForm form, BindException errors)
}
}

private boolean renamePreviousFolder(Submission previousSubmission, String targetContainerName, BindException errors)
private String renamePreviousFolder(Submission previousSubmission, String targetContainerName, BindException errors)
{
String newPath = null;
ExperimentAnnotations previousCopy = ExperimentAnnotationsManager.get(previousSubmission.getCopiedExperimentId());
if (previousCopy != null)
{
Container previousContainer = previousCopy.getContainer();
newPath = previousContainer.getPath();
if (targetContainerName.equals(previousContainer.getName()))
{
try (DbScope.Transaction transaction = PanoramaPublicManager.getSchema().getScope().ensureTransaction())
Expand All @@ -1601,21 +1624,23 @@ private boolean renamePreviousFolder(Submission previousSubmission, String targe
{
errors.reject(ERROR_MSG, "Previous experiment copy (Id: " + previousCopy.getId() + ") does not have a version. " +
"Cannot rename previous folder.");
return false;
return null;
}
// Rename the container where the old copy lives so that the same folder name can be used for the new copy.
String newName = previousContainer.getName() + " V." + version;
if (ContainerManager.getChild(previousContainer.getParent(), newName) != null)
{
errors.reject(ERROR_MSG, "Cannot rename previous folder to '" + newName + "'. A folder with that name already exists.");
return false;
return null;
}
ContainerManager.rename(previousContainer, getUser(), newName);

newPath = FileContentService.get().getFileRoot(previousContainer.getParent()) + File.separator + newName;
transaction.commit();
}
}
}
return true;
return newPath;
}

private ValidEmail getValidEmail(String email, String errMsg, BindException errors)
Expand Down Expand Up @@ -1683,6 +1708,8 @@ public static class CopyExperimentForm extends ExperimentIdForm
private boolean _usePxTestDb; // Use the test database for getting a PX ID if true
private boolean _assignDoi;
private boolean _useDataCiteTestApi;

private boolean _moveAndSymlink;
private boolean _deleteOldCopy;

static void setDefaults(CopyExperimentForm form, ExperimentAnnotations sourceExperiment, Submission currentSubmission)
Expand All @@ -1696,6 +1723,7 @@ static void setDefaults(CopyExperimentForm form, ExperimentAnnotations sourceExp
form.setUsePxTestDb(false);

form.setAssignDoi(true);
form.setMoveAndSymlink(true);
form.setUseDataCiteTestApi(false);

Container sourceExptContainer = sourceExperiment.getContainer();
Expand Down Expand Up @@ -1819,6 +1847,16 @@ public void setUseDataCiteTestApi(boolean useDataCiteTestApi)
_useDataCiteTestApi = useDataCiteTestApi;
}

public boolean isMoveAndSymlink()
{
return _moveAndSymlink;
}

public void setMoveAndSymlink(boolean moveAndSymlink)
{
_moveAndSymlink = moveAndSymlink;
}

public boolean isDeleteOldCopy()
{
return _deleteOldCopy;
Expand Down Expand Up @@ -5626,7 +5664,7 @@ public ExperimentAnnotationsDetails(User user, ExperimentAnnotations exptAnnotat
{
// Display the version only if there is more than one version of this dataset on Panorama Public
_version = _experimentAnnotations.getStringVersion(maxVersion);
if (_experimentAnnotations.getDataVersion().equals(maxVersion))
if (_experimentAnnotations.getDataVersion() != null && _experimentAnnotations.getDataVersion().equals(maxVersion))
{
// This is the current version; Display a link to see all published versions
_versionsUrl = new ActionURL(PanoramaPublicController.ShowPublishedVersions.class, _experimentAnnotations.getContainer());
Expand Down Expand Up @@ -9050,6 +9088,20 @@ public Pair<AttachmentParent, String> getAttachment(AttachmentForm form)
}
}

@RequiresPermission(ReadPermission.class)
public static class VerifySymlinksAction extends ReadOnlyApiAction<CatalogForm>
{
@Override
public Object execute(CatalogForm catalogForm, BindException errors) throws Exception
{
if (PanoramaPublicSymlinkManager.get().verifySymlinks())
return success();

errors.reject(ERROR_MSG, "Problems with symlink registration. See log for details.");
return null;
}
}

@RequiresPermission(ReadPermission.class)
public static class GetCatalogApiAction extends ReadOnlyApiAction<CatalogForm>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package org.labkey.panoramapublic;

import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.admin.AbstractFolderImportFactory;
import org.labkey.api.admin.FolderImportContext;
import org.labkey.api.admin.FolderImporter;
import org.labkey.api.admin.ImportException;
import org.labkey.api.admin.SubfolderWriter;
import org.labkey.api.data.Container;
import org.labkey.api.exp.api.ExpData;
import org.labkey.api.exp.api.ExpRun;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.files.FileContentService;
import org.labkey.api.pipeline.PipelineJob;
import org.labkey.api.pipeline.PipelineService;
import org.labkey.api.query.BatchValidationException;
import org.labkey.api.security.User;
import org.labkey.api.writer.VirtualFile;
import org.labkey.panoramapublic.pipeline.CopyExperimentPipelineJob;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;

/**
* This importer does a file move instead of copy to the temp directory and creates a symlink in place of the original
* file.
*/
public class PanoramaPublicFileImporter implements FolderImporter
{
@Override
public String getDataType()
{
return PanoramaPublicManager.PANORAMA_PUBLIC_FILES;
}

@Override
public String getDescription()
{
return "Panorama Public Files";
}

@Override
public void process(@Nullable PipelineJob job, FolderImportContext ctx, VirtualFile root) throws Exception
{
Logger log = ctx.getLogger();

FileContentService fcs = FileContentService.get();
if (null == fcs)
return;

File targetRoot = fcs.getFileRoot(ctx.getContainer());

if (null == targetRoot)
{
log.error("File copy target folder not found: " + ctx.getContainer().getPath());
return;
}

if (null == job)
{
log.error("Pipeline job not found.");
return;
}

if (job instanceof CopyExperimentPipelineJob expJob)
{
File targetFiles = new File(targetRoot.getPath(), FileContentService.FILES_LINK);

// Get source files including resolving subfolders
String divider = FileContentService.FILES_LINK + File.separator + PipelineService.EXPORT_DIR;
String subProject = root.getLocation().substring(root.getLocation().lastIndexOf(divider) + divider.length());
subProject = subProject.replace(File.separator + SubfolderWriter.DIRECTORY_NAME, "");

Path sourcePath = Paths.get(fcs.getFileRoot(expJob.getExportSourceContainer()).getPath(), subProject);
File sourceFiles = Paths.get(sourcePath.toString(), FileContentService.FILES_LINK).toFile();

if (!targetFiles.exists())
{
log.warn("Panorama public file copy target not found. Creating directory: " + targetFiles);
Files.createDirectories(targetFiles.toPath());
}

log.info("Moving files and creating sym links in folder " + ctx.getContainer().getPath());
PanoramaPublicSymlinkManager.get().moveAndSymLinkDirectory(expJob, sourceFiles, targetFiles, false, log);

alignDataFileUrls(expJob.getUser(), ctx.getContainer(), log);
}
}

private void alignDataFileUrls(User user, Container targetContainer, Logger log) throws BatchValidationException, ImportException
{
log.info("Aligning data files urls in folder: " + targetContainer.getPath());

FileContentService fcs = FileContentService.get();
if (null == fcs)
return;

ExperimentService expService = ExperimentService.get();
List<? extends ExpRun> runs = expService.getExpRuns(targetContainer, null, null);
boolean errors = false;

Path fileRootPath = fcs.getFileRootPath(targetContainer, FileContentService.ContentType.files);
if(fileRootPath == null || !Files.exists(fileRootPath))
{
throw new ImportException("File root path for container " + targetContainer.getPath() + " does not exist: " + fileRootPath);
}

for (ExpRun run : runs)
{
run.setFilePathRootPath(fileRootPath);
run.save(user);
log.debug("Setting filePathRoot on copied run: " + run.getName() + " to: " + fileRootPath);

for (ExpData data : run.getAllDataUsedByRun())
{
if (null != data.getRun() && data.getDataFileUrl().contains(FileContentService.FILES_LINK))
{
String[] parts = Objects.requireNonNull(data.getFilePath()).toString().split("Run\\d+");

if (parts.length > 1)
{
String fileName = parts[1];
Path newDataPath = Paths.get(fileRootPath.toString(), fileName);

if (newDataPath.toFile().exists())
{
data.setDataFileURI(newDataPath.toUri());
data.save(user);
log.debug("Setting dataFileUri on copied data: " + data.getName() + " to: " + newDataPath);
}
else
{
log.error("Data file not found: " + newDataPath.toUri());
errors = true;
}
}
else
{
log.error("Unexpected data file path. Could not align dataFileUri. " + data.getFilePath().toString());
errors = true;
}
}
}
}
if (errors)
{
throw new ImportException("Data files urls could not be aligned.");
}
}

public static class Factory extends AbstractFolderImportFactory
{
@Override
public FolderImporter create()
{
return new PanoramaPublicFileImporter();
}

@Override
public int getPriority()
{
// We want this to run last to do exp.data.datafileurl cleanup
return PanoramaPublicManager.PRIORITY_PANORAMA_PUBLIC_FILES;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.labkey.panoramapublic;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.labkey.api.data.Container;
import org.labkey.api.data.SQLFragment;
import org.labkey.api.exp.api.ExpData;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.files.FileListener;
import org.labkey.api.security.User;

import java.io.File;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;

public class PanoramaPublicFileListener implements FileListener
{

@Override
public String getSourceName()
{
return null;
}

@Override
public void fileCreated(@NotNull File created, @Nullable User user, @Nullable Container container)
{

}

@Override
public int fileMoved(@NotNull File src, @NotNull File dest, @Nullable User user, @Nullable Container container)
{
// Update any symlinks targeting the file
PanoramaPublicSymlinkManager.get().fireSymlinkUpdate(src.toPath(), dest.toPath(), container);

ExpData data = ExperimentService.get().getExpDataByURL(src, null);
if (null != data)
data.setDataFileURI(dest.toURI());

return 0;
}

@Override
public void fileDeleted(@NotNull Path deleted, @Nullable User user, @Nullable Container container)
{
ExpData data = ExperimentService.get().getExpDataByURL(deleted, container);

if (null != data)
data.delete(user);
}

@Override
public Collection<File> listFiles(@Nullable Container container)
{
return Collections.emptyList();
}

@Override
public SQLFragment listFilesQuery()
{
throw new UnsupportedOperationException("Not implemented");
}
}
Loading