Skip to content

Commit

Permalink
show the dialog to upgrade data loader if newer version is available
Browse files Browse the repository at this point in the history
Based on the ask at https://ideas.salesforce.com/s/idea/a0B8W00000GdVkgUAF/data-loader-upgrade-option-should-be-there-in-the-application-itself

- show the dialog with the link to Data Loader download page if a newer version is available for download.
  • Loading branch information
ashitsalesforce committed Oct 28, 2023
1 parent c831950 commit 141cbbd
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 65 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>com.force</groupId>
<artifactId>dataloader</artifactId>
<packaging>jar</packaging>
<version>59.0.0</version>
<version>59.0.1</version>
<name>Salesforce Data Loader</name>
<url>https://github.com/forcedotcom/dataloader</url>
<organization>
Expand Down
116 changes: 116 additions & 0 deletions src/main/java/com/salesforce/dataloader/ui/LoaderDownloadDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2015, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

package com.salesforce.dataloader.ui;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.*;

import com.salesforce.dataloader.util.AppUtil;

/**
* Splash screen for the loader.
*
* @author Lexi Viripaeff
* @since 6.0
*/
public class LoaderDownloadDialog extends LoaderTitleAreaDialog {


/**
* MyTitleAreaDialog constructor
*
* @param shell
* the parent shell
*/
public LoaderDownloadDialog(Shell activeShell) {
super(activeShell);
}

/**
* Creates the dialog's contents
*
* @param parent
* the parent composite
* @return Control
*/
@Override
protected Control createContents(Composite parent) {
Control contents = super.createContents(parent);

// Set the title
setTitle(Labels.getString("LoaderDownloadDialog.title")); //$NON-NLS-1$

// Set the message
setMessage(Labels.getFormattedString("LoaderDownloadDialog.messageLineOne",
new String[] {AppUtil.getLatestDownloadableDataLoaderVersion(),
AppUtil.DATALOADER_DOWNLOAD_URL})); //$NON-NLS-1$

// Set the image
setTitleImage(UIUtils.getImageRegistry().get("splashscreens")); //$NON-NLS-1$

return contents;
}

/**
* Creates the gray area
*
* @param parent
* the parent composite
* @return Control
*/
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite)super.createDialogArea(parent);

return composite;
}

/**
* Creates the buttons for the button bar
*
* @param parent
* the parent composite
*/
@Override
protected void createButtonsForButtonBar(Composite parent) {
// create all the buttons, in order

createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
}

/**
* Sets the button behavior
*/
@Override
protected void buttonPressed(int buttonID) {
setReturnCode(buttonID);
close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
Expand Down Expand Up @@ -83,7 +85,7 @@ public static Image getImageFromRegistry(String imgKey) {
Color titleAreaColor;
private String message = ""; //$NON-NLS-1$
private String errorMessage;
private Text messageLabel;
private Link messageLabel;
private Composite workArea;
private Label messageImageLabel;
private Image messageImage;
Expand Down Expand Up @@ -233,7 +235,7 @@ public void widgetDisposed(DisposeEvent e) {
messageImageLabel.setBackground(background);

// Message label @ bottom, center
messageLabel = new Text(parent, SWT.WRAP | SWT.READ_ONLY);
messageLabel = new Link(parent, SWT.WRAP | SWT.READ_ONLY);
JFaceColors.setColors(messageLabel, foreground, background);
messageLabel.setText(" \n "); // two lines//$NON-NLS-1$
messageLabel.setFont(JFaceResources.getDialogFont());
Expand Down Expand Up @@ -517,6 +519,12 @@ private void updateMessage(String newMessage) {
if (newMessage != null && newMessage.indexOf('\n') == -1)
newMessage = newMessage + "\n "; //$NON-NLS-1$
messageLabel.setText(newMessage);
messageLabel.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
UIUtils.openURL(e.text);
}
});
}
/**
* Sets the title to be shown in the title area of this dialog.
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/com/salesforce/dataloader/ui/LoaderWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.salesforce.dataloader.config.Config.ConfigListener;
import com.salesforce.dataloader.controller.Controller;
import com.salesforce.dataloader.ui.uiActions.*;
import com.salesforce.dataloader.util.AppUtil;

/**
* The main class for the Loader UI.
Expand Down Expand Up @@ -199,7 +200,8 @@ protected Control createContents(Composite parent) {
if (!config.getBoolean(Config.HIDE_WELCOME_SCREEN)) {
displayTitleDialog(Display.getDefault(), this.operationActionsByIndex, this.controller.getConfig());
}

displayUpgradeDialog(Display.getDefault());

comp.pack();
parent.pack();

Expand Down Expand Up @@ -284,6 +286,19 @@ public void run() {
}
});
}

private void displayUpgradeDialog(final Display display) {
if (AppUtil.DATALOADER_VERSION.equals(AppUtil.getLatestDownloadableDataLoaderVersion())) {
return; // running app's version matches with downloadable version.
}
display.asyncExec(new Thread() {
@Override
public void run() {
LoaderDownloadDialog dlg = new LoaderDownloadDialog(display.getActiveShell());
int result = dlg.open();
}
});
}

/**
* Creates the menu for the application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public boolean open() throws UnsupportedEncodingException {
link.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
loginRunner.openURL(e.text);
UIUtils.openURL(e.text);
}
});
final Clipboard clipboard = new Clipboard(display);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/com/salesforce/dataloader/ui/UIUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
package com.salesforce.dataloader.ui;

import com.salesforce.dataloader.action.OperationInfo;
import com.salesforce.dataloader.util.AppUtil;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.SWT;
Expand All @@ -37,13 +40,16 @@
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;

import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

public class UIUtils {
private static ImageRegistry image_registry;
private static Logger logger = LogManager.getLogger(AppUtil.class);

public static boolean isValidHttpsUrl(String url) {
try {
Expand Down Expand Up @@ -135,4 +141,61 @@ public static String[] setComboItems(Combo combo, List<String> itemList, String
}
return itemArray;
}


public static void openURL(String url) {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
try {
logger.debug("trying to use desktop.browse() method");
desktop.browse(new URI(url));
} catch (Exception e) {
logger.debug(e.getMessage());
openURLUsingNativeCommand(url);
}
} else {
logger.debug("trying to use native command");
openURLUsingNativeCommand(url);
}
}

private static void openURLUsingNativeCommand(String url) {
Runtime runtime = Runtime.getRuntime();
String osName = System.getProperty("os.name");
try {
if (osName.toLowerCase().indexOf("mac") >= 0) {
logger.debug("trying to use open command on mac");
runtime.exec("open " + url);
}
else if (osName.toLowerCase().indexOf("win") >= 0) {
logger.debug("trying to use rundll32 command on windows");
runtime.exec("rundll32 url.dll,FileProtocolHandler " + url);
} else { //assume Unix or Linux
try {
logger.debug("trying to use xdg-open command on linux");
runtime.exec("xdg-open " + url);
} catch (IOException e) {
logger.debug(e.getMessage());
logger.debug("trying to browser-specific command on linux");
String[] browsers = {
"firefox", "chrome", "opera", "konqueror", "epiphany", "mozilla", "netscape" };
String browser = null;
for (int count = 0; count < browsers.length && browser == null; count++)
if (runtime.exec(
new String[] {"which", browsers[count]}).waitFor() == 0) {
browser = browsers[count];
}
if (browser == null) {
throw new Exception("Could not find web browser");
} else {
runtime.exec(new String[] {browser, url});
}
}
}
}
catch (Exception e) {
logger.error(e.getMessage());
}
}

}
49 changes: 48 additions & 1 deletion src/main/java/com/salesforce/dataloader/util/AppUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
Expand All @@ -45,6 +53,8 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.FactoryConfigurationError;

Expand All @@ -54,7 +64,7 @@
import com.salesforce.dataloader.config.Config;
import com.salesforce.dataloader.config.Messages;
import com.salesforce.dataloader.exception.ConfigInitializationException;
import com.salesforce.dataloader.ui.Labels;


/**
* com.salesforce.dataloader.util
Expand Down Expand Up @@ -87,9 +97,11 @@ public enum APP_RUN_MODE {
public static final String CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH = "swt.nativelib.inpath";
public static final String CLI_OPTION_CONFIG_DIR_PROP = "salesforce.config.dir";
public static final String CONFIG_DIR_DEFAULT_VALUE = "configs";
public static final String DATALOADER_DOWNLOAD_URL = "https://developer.salesforce.com/tools/data-loader";

private static APP_RUN_MODE appRunMode = APP_RUN_MODE.UI;
private static Logger logger = null;
private static String latestDownloadableDataLoaderVersion;

static {
Properties versionProps = new Properties();
Expand All @@ -113,9 +125,14 @@ public static String[] initializeAppConfig(String[] args) throws FactoryConfigur
LoggingUtil.initializeLog(argsMap);
logger = LogManager.getLogger(AppUtil.class);
setUseGMTForDateFieldValue(argsMap);
latestDownloadableDataLoaderVersion = _getLatestAvailableAppVersionFromWebsite();
return convertCommandArgsMapToArgsArray(argsMap);
}

public static String getLatestDownloadableDataLoaderVersion() {
return latestDownloadableDataLoaderVersion;
}

public static OSType getOSType() throws SecurityException {
String osName = System.getProperty(OS_NAME);

Expand Down Expand Up @@ -415,4 +432,34 @@ public static int exec(List<String> command, String exceptionMessage) {
}
return exitVal;
}

private static final String DL_DOWNLOADABLE_REGEX = "[0-9]+\\.[0-9]+\\.[0-9]+\\.zip";
private static String _getLatestAvailableAppVersionFromWebsite() {
try {
Builder requestBuilder = HttpRequest.newBuilder(new URI(DATALOADER_DOWNLOAD_URL));
HttpRequest request = requestBuilder.GET().build();
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.proxy(ProxySelector.getDefault())
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
if (response.statusCode() != HttpURLConnection.HTTP_OK) {
logger.info("Unable to check for the latest available data loader version. Response code = " + response.statusCode());
return DATALOADER_VERSION;
}
String responseContent = response.body();

Pattern htmlTagInRichTextPattern = Pattern.compile(DL_DOWNLOADABLE_REGEX);
Matcher matcher = htmlTagInRichTextPattern.matcher(responseContent);
String downloadableVersion = DATALOADER_VERSION;
if (matcher.find()) {
downloadableVersion = matcher.group();
downloadableVersion = downloadableVersion.substring(0, downloadableVersion.lastIndexOf("."));
}
return downloadableVersion;
} catch (Exception e) {
logger.info("Unable to check for the latest available data loader version: " + e.getMessage());
return DATALOADER_VERSION;
}
}
}
Loading

0 comments on commit 141cbbd

Please sign in to comment.