Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IQSS/9095-dvwebloader integration #9096

1 change: 1 addition & 0 deletions doc/release-notes/9096-folder-upload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dataverse can now support upload of an entire folder tree of files and retain the relative paths of files as directory path metadata for the uploaded files.
qqmyers marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2730,6 +2730,7 @@ The URL for your Repository Storage Abstraction Layer (RSAL) installation. This
This setting controls which upload methods are available to users of your Dataverse installation. The following upload methods are available:

- ``native/http``: Corresponds to "Upload with HTTP via your browser" and APIs that use HTTP (SWORD and native).
- ``dvwebloader``: Corresponds to :ref:`folder-upload`. Note that ``dataverse.files.<id>.download-redirect`` must be set to "true" on an S3 store for this method to show up in the UI. In addition, :ref:`:WebloaderUrl` must be set.
- ``dcm/rsync+ssh``: Corresponds to "Upload with rsync+ssh via Data Capture Module (DCM)". A lot of setup is required, as explained in the :doc:`/developers/big-data-support` section of the Developer Guide.

Out of the box only ``native/http`` is enabled and will work without further configuration. To add multiple upload method, separate them using a comma like this:
Expand Down Expand Up @@ -3231,6 +3232,11 @@ The interval in seconds between Dataverse calls to Globus to check on upload pro

A true/false option to add a Globus transfer option to the file download menu which is not yet fully supported in the dataverse-globus app. See :ref:`globus-support` for details.

.. _:WebloaderUrl:

:WebloaderUrl
+++++++++++++

The URL for main HTML file in https://github.com/gdcc/dvwebloader when that app is deployed. See also :ref:`:UploadMethods` for another required settings.

.. _supported MicroProfile Config API source: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html
9 changes: 8 additions & 1 deletion doc/sphinx-guides/source/user/dataset-management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ Dropbox Upload

Some Dataverse installations support the ability to upload files directly from Dropbox. To do so, click the "Upload from Dropbox" button, log in to Dropbox in the pop-up window, and select the files you'd like to transfer over.

.. _folder-upload:

Folder Upload
-------------

Some Dataverse installations support the ability to upload some/all files from a local folder and subfolders. To do this, click the "Upload from Folder" button, select the folder you wish to upload, select/unselect specific files, and click 'Start Uploads'. More detailed instructions are available in the `DVWebloader wiki <https://github.com/gdcc/dvwebloader/wiki#use>`_.
qqmyers marked this conversation as resolved.
Show resolved Hide resolved

.. _rsync_upload:

rsync + SSH Upload
Expand Down Expand Up @@ -268,7 +275,7 @@ After you :ref:`upload your files <dataset-file-upload>`, you can apply a "Workf
|cw-image4|

How to Describe Your Computational Workflow
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Dataverse installation you are using may have enabled Computational Workflow metadata fields for your use. If so, when :ref:`editing your dataset metadata <adding-new-dataset>`, you will see the fields described below.

Expand Down
21 changes: 20 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@

import edu.harvard.iq.dataverse.util.StringUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
import edu.harvard.iq.dataverse.util.URLTokenUtil;
import edu.harvard.iq.dataverse.util.WebloaderUtil;
import edu.harvard.iq.dataverse.validation.URLValidator;
import edu.harvard.iq.dataverse.workflows.WorkflowComment;

Expand Down Expand Up @@ -1845,7 +1847,9 @@ public boolean globusUploadSupported() {
return settingsWrapper.isGlobusUpload() && settingsWrapper.isGlobusEnabledStorageDriver(dataset.getEffectiveStorageDriverId());
}


public boolean webloaderUploadSupported() {
return settingsWrapper.isWebloaderUpload() && StorageIO.isDirectUploadEnabled(dataset.getEffectiveStorageDriverId());
}

private String init(boolean initFull) {

Expand Down Expand Up @@ -6062,4 +6066,19 @@ public void startGlobusTransfer() {
}
PrimeFaces.current().executeScript(globusService.getGlobusDownloadScript(dataset, apiToken));
}

public String getWebloaderUrlForDataset(Dataset d) {
String localeCode = session.getLocaleCode();
User user = session.getUser();
if (user instanceof AuthenticatedUser) {
ApiToken apiToken = authService.getValidApiTokenForUser((AuthenticatedUser) user);
return WebloaderUtil.getWebloaderUrl(d, apiToken, localeCode,
settingsService.getValueForKey(SettingsServiceBean.Key.WebloaderUrl));
} else {
// Shouldn't normally happen (seesion timeout? bug?)
logger.warning("getWebloaderUrlForDataset called for non-Authenticated user");
return null;
}
}

}
22 changes: 22 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import edu.harvard.iq.dataverse.api.AbstractApiBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.branding.BrandingUtil;
import edu.harvard.iq.dataverse.datasetutility.AddReplaceFileHelper;
import edu.harvard.iq.dataverse.datasetutility.FileSizeChecker;
Expand Down Expand Up @@ -36,6 +38,8 @@
import edu.harvard.iq.dataverse.util.FileUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import edu.harvard.iq.dataverse.util.SystemConfig;
import edu.harvard.iq.dataverse.util.URLTokenUtil;
import edu.harvard.iq.dataverse.util.WebloaderUtil;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.EjbUtil;
import edu.harvard.iq.dataverse.util.FileMetadataUtil;
Expand Down Expand Up @@ -3067,6 +3071,10 @@ public boolean globusUploadSupported() {
return settingsWrapper.isGlobusUpload()
&& settingsWrapper.isGlobusEnabledStorageDriver(dataset.getEffectiveStorageDriverId());
}

public boolean webloaderUploadSupported() {
return settingsWrapper.isWebloaderUpload() && StorageIO.isDirectUploadEnabled(dataset.getEffectiveStorageDriverId());
}

private void populateFileMetadatas() {
fileMetadatas = new ArrayList<>();
Expand Down Expand Up @@ -3106,4 +3114,18 @@ public void setFileAccessRequest(boolean fileAccessRequest) {
public boolean isHasPublicStore() {
return settingsWrapper.isTrueForKey(SettingsServiceBean.Key.PublicInstall, StorageIO.isPublicStore(dataset.getEffectiveStorageDriverId()));
}

public String getWebloaderUrlForDataset(Dataset d) {
String localeCode = session.getLocaleCode();
User user = session.getUser();
if (user instanceof AuthenticatedUser) {
ApiToken apiToken = authService.getValidApiTokenForUser((AuthenticatedUser) user);
return WebloaderUtil.getWebloaderUrl(d, apiToken, localeCode,
settingsService.getValueForKey(SettingsServiceBean.Key.WebloaderUrl));
} else {
// Shouldn't normally happen (seesion timeout? bug?)
logger.warning("getWebloaderUrlForDataset called for non-Authenticated user");
return null;
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public class SettingsWrapper implements java.io.Serializable {

private Boolean rsyncOnly = null;

private Boolean webloaderUpload = null;

private String metricsUrl = null;

private Boolean dataFilePIDSequentialDependent = null;
Expand Down Expand Up @@ -338,6 +340,13 @@ public String getGlobusAppUrl() {

}

public boolean isWebloaderUpload() {
if (webloaderUpload == null) {
webloaderUpload = systemConfig.isWebloaderUpload();
}
return webloaderUpload;
}

public boolean isRsyncOnly() {
if (rsyncOnly == null) {
String downloadMethods = getValueForKey(SettingsServiceBean.Key.DownloadMethods);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -940,4 +940,14 @@ public List <WorkflowComment> getWorkflowCommentsByAuthenticatedUser(Authenticat
return query.getResultList();
}

public ApiToken getValidApiTokenForUser(AuthenticatedUser user) {
ApiToken apiToken = null;
apiToken = findApiTokenByUser(user);
if ((apiToken == null) || (apiToken.getExpireTime().before(new Date()))) {
logger.fine("Created apiToken for user: " + user.getIdentifier());
apiToken = generateApiTokenForUser(user);
}
return apiToken;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -563,11 +563,16 @@ Whether Harvesting (OAI) service is enabled
/*
* Allow a custom JavaScript to control values of specific fields.
*/
ControlledVocabularyCustomJavaScript,
ControlledVocabularyCustomJavaScript,
/**
* A compound setting for disabling signup for remote Auth providers:
*/
AllowRemoteAuthSignUp
AllowRemoteAuthSignUp,
/**
* The URL for the DvWebLoader tool (see github.com/gdcc/dvwebloader for details)
*/
WebloaderUrl
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed yesterday, we should probably get in the habit of using MPCONFIG, and not just to make @poikilotherm happy. 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read the existing code right, the new classes can replace a JVM property but code to replace settings (and keep the ability to dynamically change them) isn't in place. If it is, please point me at an example. (FWIW: #9175 was my first mpconfig jvm option and it appears to work so I'll try to keep adding mpconfig for those).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. @poikilotherm I just moved this to QA but if you have ideas, please let us know. Thanks.


;

@Override
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,13 @@ public enum FileUploadMethods {
* Upload through Globus of large files
*/

GLOBUS("globus")
GLOBUS("globus"),

/**
* Upload folders of files through dvwebloader app
*/

WEBLOADER("dvwebloader");
;


Expand Down Expand Up @@ -898,6 +904,10 @@ public boolean isRsyncUpload(){
public boolean isGlobusUpload(){
return getMethodAvailable(FileUploadMethods.GLOBUS.toString(), true);
}

public boolean isWebloaderUpload(){
return getMethodAvailable(FileUploadMethods.WEBLOADER.toString(), true);
}

// Controls if HTTP upload is enabled for both GUI and API.
public boolean isHTTPUpload(){
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/util/WebloaderUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package edu.harvard.iq.dataverse.util;

import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetPage;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;

public class WebloaderUtil {

private static final Logger logger = Logger.getLogger(WebloaderUtil.class.getCanonicalName());

/**
* Create the URL required to launch https://github.com/gdcc/dvweloader
qqmyers marked this conversation as resolved.
Show resolved Hide resolved
*/
public static String getWebloaderUrl(Dataset d, ApiToken apiToken, String localeCode, String baseUrl) {
// Use URLTokenUtil for params currently in common with external tools.
URLTokenUtil tokenUtil = new URLTokenUtil(d, apiToken, localeCode);
String appUrl;
appUrl = baseUrl
+ "?datasetPid={datasetPid}&siteUrl={siteUrl}&key={apiToken}&datasetId={datasetId}&datasetVersion={datasetVersion}&dvLocale={localeCode}";
return tokenUtil.replaceTokensWithValues(appUrl);
}
}
4 changes: 4 additions & 0 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,10 @@ file.finishGlobus=Globus Transfer has finished
file.downloadFromGlobus=Download through Globus
file.globus.transfer=Globus Transfer
file.globus.of=of:
file.fromWebloader.tip=Upload a folder of files. This method retains the relative path structure on from your local machine. (Using it will cancel any other types of uploads in progress on this page.)
qqmyers marked this conversation as resolved.
Show resolved Hide resolved
file.fromWebloaderAfterCreate.tip=This option will be enabled after this dataset is created.
qqmyers marked this conversation as resolved.
Show resolved Hide resolved
file.fromWebloader=Upload a Folder

file.api.httpDisabled=File upload via HTTP is not available for this installation of Dataverse.
file.api.alreadyHasPackageFile=File upload via HTTP disabled since this dataset already contains a package file.
file.replace.original=Original File
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/dataset.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@
<ui:param name="editDatafilesPage" value="false"/>
<ui:param name="rsyncSupported" value="#{DatasetPage.rsyncUploadSupported()}"/>
<ui:param name="globusUploadSupported" value="#{DatasetPage.globusUploadSupported()}"/>
<ui:param name="webloaderUploadSupported" value="#{DatasetPage.webloaderUploadSupported()}"/>
<ui:param name="dataverse" value="#{DatasetPage.dataset.owner}"/>
<ui:param name="dataset" value="#{DatasetPage.dataset}"/>
<ui:param name="version" value="#{DatasetPage.workingVersion}"/>
Expand Down
13 changes: 9 additions & 4 deletions src/main/webapp/editFilesFragment.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,26 @@
widgetVar="fileUploadWidget">
<f:passThroughAttribute name="aria-label" value="#{bundle['file.uploadFiles']}"/>
</p:fileUpload>

<div jsf:id="dropboxBlock" jsf:rendered="#{settingsWrapper.isHasDropBoxKey() and !lockedFromEdits }" class="margin-top">
<ui:fragment rendered="#{!lockedFromEdits}">
<div jsf:id="webloaderBlock" jsf:rendered="#{webloaderUploadSupported}" class="ui-fileupload-buttonbar ui-widget-header ui-corner-top">
<p class="help-block" jsf:rendered="#{!createDataset}">#{bundle['file.fromWebloader.tip']}</p>
<p class="help-block" jsf:rendered="#{createDataset}">#{bundle['file.fromWebloaderAfterCreate.tip']}</p>
<p:button target="_blank" rendered="#{!createDataset}" onclick="cancelDatasetEdit();window.open('#{(datasetPage==true) ? DatasetPage.getWebloaderUrlForDataset(dataset):EditDatafilesPage.getWebloaderUrlForDataset(dataset)}');return false;" value="#{bundle['file.fromWebloader']}" icon="ui-icon-plusthick" />
</div>
<div jsf:id="dropboxBlock" jsf:rendered="#{settingsWrapper.isHasDropBoxKey()}" class="margin-top">
<!-- Dropbox upload widget -->
<p class="help-block">#{bundle['file.fromDropbox.tip']}</p>
<h:inputText id="dropBoxSelectionInput" style="display:none" value="#{EditDatafilesPage.dropBoxSelection}"/>
<p:commandButton id="dropBoxButton" actionListener="#{EditDatafilesPage.handleDropBoxUpload}" oncomplete="javascript:dropBoxUploadFinished();" update="@none" style="display:none;" />
<p:commandButton id="dropBoxUserButton" disabled="#{!(datasetPage || EditDatafilesPage.showFileUploadComponent())}" value="#{bundle['file.fromDropbox']}" onclick="openDropboxChooser();" icon="dropin-btn-status" />
<p:message for="dropBoxButton" id="dropBoxUploadMessage" display="text" redisplay="false" />
</div>
<div jsf:id="globusBlock" jsf:rendered="#{globusUploadSupported and !lockedFromEdits }" class="margin-top">
<div jsf:id="globusBlock" jsf:rendered="#{globusUploadSupported}" class="margin-top">
<p class="help-block" jsf:rendered="#{!createDataset}">#{bundle['file.fromGlobus.tip']}</p>
<p class="help-block" jsf:rendered="#{createDataset}">#{bundle['file.fromGlobusAfterCreate.tip']}</p>
<p:button target="_blank" rendered="#{!createDataset}" onclick="cancelDatasetEdit();window.open('#{GlobusServiceBean.getGlobusAppUrlForDataset(dataset)}');return false;" value="#{bundle['file.fromGlobus']}" icon="globus-btn" />
</div>

</ui:fragment>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/editdatafiles.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<ui:param name="showFileButtonUpdate" value="true"/>
<ui:param name="rsyncSupported" value="#{EditDatafilesPage.rsyncUploadSupported()}"/>
<ui:param name="globusUploadSupported" value="#{EditDatafilesPage.globusUploadSupported()}"/>
<ui:param name="webloaderUploadSupported" value="#{EditDatafilesPage.webloaderUploadSupported()}"/>
<ui:param name="lockedFromEdits" value="#{EditDatafilesPage.lockedFromEdits}"/>
</ui:include>
</div>
Expand Down
1 change: 0 additions & 1 deletion src/main/webapp/resources/css/structure.css
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,6 @@ div.panel-body.read-terms{max-height:220px; overflow-y:scroll; width:100%; backg
.dropin-btn-status.ui-icon {background: url("https://www.dropbox.com/static/images/widgets/dbx-saver-status.png") no-repeat;}
.globus-btn.ui-icon {background: url("https://docs.globus.org/images/home/transfer.png") no-repeat;background-size:contain;display:inline-block;}


/* VERSIONS */
div[id$="versionsTable"] th.col-select-width * {display:none;}
#version-details-block > div {width:35%; padding:4px 10px; border: 1px solid #DDDDDD;}
Expand Down