blob: e361782b62fc6e3f9fba5502f0eae4280b4af3bb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.extbrowser.plugins.chrome;
import java.awt.Dialog;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import org.json.simple.JSONObject;
import org.netbeans.modules.extbrowser.plugins.ExtensionManager;
import org.netbeans.modules.extbrowser.plugins.ExtensionManager.ExtensitionStatus;
import org.netbeans.modules.extbrowser.plugins.ExtensionManagerAccessor;
import org.netbeans.modules.extbrowser.plugins.ExternalBrowserPlugin;
import org.netbeans.modules.extbrowser.plugins.Message;
import org.netbeans.modules.extbrowser.plugins.Message.MessageType;
import org.netbeans.modules.extbrowser.plugins.MessageListener;
import org.netbeans.modules.extbrowser.plugins.Utils;
import org.netbeans.modules.web.browser.api.BrowserFamilyId;
import org.netbeans.modules.web.browser.api.WebBrowser;
import org.netbeans.modules.web.browser.api.WebBrowsers;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.HtmlBrowser;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
/**
* @author ads
*
*/
public class ChromeManagerAccessor implements ExtensionManagerAccessor {
static final Logger LOGGER = Logger.getLogger(ChromeManagerAccessor.class.getName());
private static final String NO_WEB_STORE_SWITCH=
"netbeans.extbrowser.manual_chrome_plugin_install"; // NOI18N
private static final String PLUGIN_PAGE=
"https://chrome.google.com/webstore/detail/netbeans-connector/hafdlehgocfcodbgjnpecfajgkeejnaa"; //NOI18N
/* (non-Javadoc)
* @see org.netbeans.modules.web.plugins.ExtensionManagerAccessor#getManager()
*/
@Override
public BrowserExtensionManager getManager() {
return new ChromeExtensionManager();
}
static class ChromeExtensionManager extends AbstractBrowserExtensionManager {
private static final String VERSION = "\"version\":"; // NOI18N
private static final String PLUGIN_PUBLIC_KEY =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgo89CrO8f/2srD2BGUP9+dG4I" +
"kTC2D3gzEjXITaMaQgy8B4ObVpkA3bc27qc7HB9jhVj8P51aAETr89u+AvFgCeFt" +
"vtva0h0oodKRC3dCkQLnEWPGi7mEKB98cRhZmQ1Wa9A9tg3plKsujwwWskaFEL/h" +
"O7uu7myF0qLIeuiG6wIDAQAB";// NOI18N
private static final String EXTENSION_PATH = "modules/lib/netbeans-chrome-connector.crx"; // NOI18N
@Override
public BrowserFamilyId getBrowserFamilyId() {
return BrowserFamilyId.CHROME;
}
/* (non-Javadoc)
* @see org.netbeans.modules.web.plugins.ExtensionManagerAccessor.BrowserExtensionManager#isInstalled()
*/
@Override
public ExtensionManager.ExtensitionStatus isInstalled() {
// Allows to skip the detection of Chrome extension
String status = System.getProperty("netbeans.chrome.connector.status"); // NOI18N
if (status != null) {
try {
return ExtensionManager.ExtensitionStatus.valueOf(status);
} catch (IllegalArgumentException iaex) {
LOGGER.log(Level.INFO, iaex.getMessage(), iaex);
}
}
while (true) {
ExtensionManager.ExtensitionStatus result = isInstalledImpl();
if (result == ExtensionManager.ExtensitionStatus.DISABLED) {
NotifyDescriptor descriptor = new NotifyDescriptor.Message(
NbBundle.getMessage(ChromeExtensionManager.class,
"LBL_ChromePluginIsDisabled"), // NOI18N
NotifyDescriptor.ERROR_MESSAGE);
descriptor.setTitle(NbBundle.getMessage(ChromeExtensionManager.class,
"TTL_ChromePluginIsDisabled")); // NOI18N
if (DialogDisplayer.getDefault().notify(descriptor) != DialogDescriptor.OK_OPTION) {
return result;
}
continue;
}
return result;
}
}
private ExtensionManager.ExtensitionStatus isInstalledImpl() {
JSONObject preferences = findPreferences();
LOGGER.log(Level.FINE, "Chrome preferences: {0}", preferences);
if (preferences == null) {
return ExtensionManager.ExtensitionStatus.MISSING;
}
LOGGER.log(Level.FINE, "Chrome preferences -> extensions: {0}", preferences.get("extensions"));
JSONObject extensions = (JSONObject)preferences.get("extensions");
if (extensions == null) {
return ExtensionManager.ExtensitionStatus.MISSING;
}
LOGGER.log(Level.FINE, "Chrome preferences -> extensions -> settings: {0}", extensions.get("settings"));
JSONObject settings = (JSONObject)extensions.get("settings");
if (settings == null) {
return ExtensionManager.ExtensitionStatus.MISSING;
}
for (Object item : settings.entrySet()) {
Map.Entry e = (Map.Entry)item;
Object value = e.getValue();
LOGGER.log(Level.FINE, "Chrome preferences - extensions -> settings -> value/extension: {0}", value);
// #251250
if (value instanceof JSONObject) {
JSONObject extension = (JSONObject) value;
String path = (String)extension.get("path");
if (path != null && (path.contains("/extbrowser.chrome/plugins/chrome")
|| path.contains("\\extbrowser.chrome\\plugins\\chrome")))
{
return ExtensionManager.ExtensitionStatus.INSTALLED;
}
LOGGER.log(Level.FINE, "Chrome preferences - extensions -> settings -> value/extension -> manifest: {0}", extension.get("manifest"));
JSONObject manifest = (JSONObject)extension.get("manifest");
if (manifest != null && PLUGIN_PUBLIC_KEY.equals((String)manifest.get("key"))) {
String version = (String)manifest.get("version");
if (isUpdateRequired( version )){
return ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE;
}
Number n = (Number)extension.get("state");
if (n != null && n.intValue() != 1) {
return ExtensionManager.ExtensitionStatus.DISABLED;
}
return ExtensionManager.ExtensitionStatus.INSTALLED;
}
}
}
return ExtensionManager.ExtensitionStatus.MISSING;
}
private JSONObject findPreferences() {
File defaultProfile = getDefaultProfile();
LOGGER.log(Level.FINE, "Chrome default profile: {0}", defaultProfile);
if (defaultProfile == null) {
return null;
}
String[] prefFiles = new String[]{"secure preferences", "protected preferences", "preferences"}; // NOI18N
for (String prefFile : prefFiles) {
File[] prefs = defaultProfile.listFiles(new FileFinder(prefFile));
if (prefs != null
&& prefs.length > 0) {
JSONObject preferences = Utils.readFile(prefs[0]);
if (preferences != null
&& preferences.get("extensions") != null) { // NOI18N
LOGGER.log(Level.FINE, "Chrome preferences file: {0}", prefs[0]);
return preferences;
}
}
}
return null;
}
@Override
public boolean install( ExtensionManager.ExtensitionStatus currentStatus )
{
File extensionFile = InstalledFileLocator.getDefault().locate(
EXTENSION_PATH,PLUGIN_MODULE_NAME, false);
if ( extensionFile == null ){
Logger.getLogger(ChromeExtensionManager.class.getCanonicalName()).
severe("Could not find chrome extension in installation directory"); // NOI18N
return false;
}
String useManualInstallation = System
.getProperty(NO_WEB_STORE_SWITCH);
if (useManualInstallation != null) {
return manualInstallPluginDialog(currentStatus, extensionFile);
}
else {
return alertGoogleWebStore(currentStatus);
}
/* NotifyDescriptor installDesc = new NotifyDescriptor.Confirmation(
NbBundle.getMessage(ChromeExtensionManager.class,
currentStatus == ExtensionManager.ExtensitionStatus.MISSING ?
"LBL_InstallMsg" : "LBL_UpgradeMsg"), // NOI18N
NbBundle.getMessage(ChromeExtensionManager.class,
"TTL_InstallExtension"), // NOI18N
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(installDesc);
if (result != NotifyDescriptor.OK_OPTION) {
return false;
}
try {
loader.requestPluginLoad( new URL("file:///"+extensionFile.getCanonicalPath()));
}
catch( IOException e ){
Logger.getLogger( ChromeExtensionManager.class.getCanonicalName()).
log(Level.INFO , null ,e );
return false;
}
return true;*/
}
@Override
protected String getCurrentPluginVersion(){
File extensionFile = InstalledFileLocator.getDefault().locate(
EXTENSION_PATH,PLUGIN_MODULE_NAME, false);
if (extensionFile == null) {
Logger.getLogger(ChromeExtensionManager.class.getCanonicalName()).
info("Could not find chrome extension in installation directory!"); // NOI18N
return null;
}
String content = Utils.readZip( extensionFile, "manifest.json"); // NOI18N
int index = content.indexOf(VERSION);
if ( index == -1){
return null;
}
index = content.indexOf(',',index);
return getValue(content, 0, index , VERSION);
}
private String getValue(String content, int start , int end , String key){
String part = content.substring( start , end );
int index = part.indexOf(key);
if ( index == -1 ){
return null;
}
String value = part.substring( index +key.length() ).trim();
return Utils.unquote(value);
}
private File getDefaultProfile() {
String[] userData = getUserData();
LOGGER.log(Level.FINE, "Chrome user data: {0}", Arrays.toString(userData));
if ( userData != null ){
for (String dataDir : userData) {
File dir = new File(dataDir);
if (dir.isDirectory() && dir.exists()) {
File[] localState = dir.listFiles(
new FileFinder("local state")); // NOI18N
boolean guessDefault = localState == null ||
localState.length == 0;
if ( !guessDefault ) {
JSONObject localStateContent = Utils.readFile( localState[0]);
if (localStateContent != null) {
JSONObject profile = (JSONObject)localStateContent.get("profile");
if (profile != null) {
String prof = (String)profile.get("last_used");
if (prof == null) {
guessDefault = true;
} else {
prof = Utils.unquote(prof);
File[] listFiles = dir.listFiles( new FileFinder(
prof , true));
if ( listFiles != null && listFiles.length >0 ){
return listFiles[0];
} else {
guessDefault = true;
}
}
}
}
}
if( guessDefault ) {
File[] listFiles = dir.listFiles(
new FileFinder("default")); // NOI18N
if ( listFiles!= null && listFiles.length >0 ) {
return listFiles[0];
}
}
}
}
}
return null;
}
protected String[] getUserData(){
// see http://www.chromium.org/user-experience/user-data-directory
// TODO - this will not work for Chromium on Windows and Mac
if (Utilities.isWindows()) {
ArrayList<String> result = new ArrayList<String>();
String localAppData = System.getenv("LOCALAPPDATA"); // NOI18N
if (localAppData != null) {
result.add(localAppData+"\\Google\\Chrome\\User Data");
} else {
localAppData = Utils.getLOCALAPPDATAonWinXP();
if (localAppData != null) {
result.add(localAppData+"\\Google\\Chrome\\User Data");
}
}
String appData = System.getenv("APPDATA"); // NOI18N
if (appData != null) {
// we are in C:\Documents and Settings\<username>\Application Data\ on XP
File f = new File(appData);
if (f.exists()) {
String fName = f.getName();
// #219824 - below code will not work on some localized WinXP where
// "Local Settings" name might be "Lokale Einstellungen";
// no harm if we try though:
f = new File(f.getParentFile(),"Local Settings");
f = new File(f, fName);
if (f.exists()) {
result.add(f.getPath()+"\\Google\\Chrome\\User Data");
}
}
}
return result.toArray(new String[result.size()]);
}
else if (Utilities.isMac()) {
return Utils.getUserPaths("/Library/Application Support/Google/Chrome");// NOI18N
}
else {
return Utils.getUserPaths("/.config/google-chrome", "/.config/chrome");// NOI18N
}
}
private boolean manualInstallPluginDialog(
ExtensionManager.ExtensitionStatus currentStatus,
File extensionFile )
{
String path;
try {
path = extensionFile.getCanonicalPath();
}
catch( IOException e ){
Logger.getLogger( ChromeExtensionManager.class.getCanonicalName()).
log(Level.INFO , null ,e );
return false;
}
JButton continueButton = new JButton(NbBundle.getMessage(
ChromeExtensionManager.class,
currentStatus == ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE ?
"LBL_ContinueUpdate" : "LBL_Continue")); // NOI18N
continueButton.getAccessibleContext().setAccessibleName(NbBundle.
getMessage(ChromeExtensionManager.class, "ACSN_Continue")); // NOI18N
continueButton.getAccessibleContext().setAccessibleDescription(NbBundle.
getMessage(ChromeExtensionManager.class, "ACSD_Continue")); // NOI18N
DialogDescriptor descriptor = new DialogDescriptor(
new ChromeInfoPanel(path, currentStatus),
NbBundle.getMessage(ChromeExtensionManager.class,
currentStatus == ExtensionManager.ExtensitionStatus.NEEDS_UPGRADE ?
"TTL_UpdateExtension" : "TTL_InstallExtension"), true,
new Object[]{continueButton,
DialogDescriptor.CANCEL_OPTION}, continueButton,
DialogDescriptor.DEFAULT_ALIGN, null, null);
InstallInfoReceiver receiver = new InstallInfoReceiver();
ExternalBrowserPlugin.getInstance().addMessageListener(receiver);
while (true) {
Object result = DialogDisplayer.getDefault().notify(descriptor);
if (result == continueButton) {
if ( receiver.isInstalled() ){
return true;
}
ExtensitionStatus status = isInstalled();
if (status == ExtensitionStatus.INSTALLED){
return true;
}
} else {
return false;
}
}
}
private boolean alertGoogleWebStore(
ExtensionManager.ExtensitionStatus currentStatus)
{
// #221325
if (currentStatus == ExtensionManager.ExtensitionStatus.MISSING) {
return alertGoogleWebStoreInstall(currentStatus);
}
// update
return alertGoogleWebStoreUpdate(currentStatus);
}
private boolean alertGoogleWebStoreInstall(final
ExtensionManager.ExtensitionStatus currentStatus)
{
final File extensionFile = InstalledFileLocator.getDefault().locate(
EXTENSION_PATH,PLUGIN_MODULE_NAME, false);
String path="";
try {
path = extensionFile.getParentFile().toURI().toURL().toExternalForm();
}
catch( MalformedURLException e ){
Logger.getLogger(ChromeExtensionManager.class.getName()).log(
Level.WARNING, null, e);
}
final Dialog[] dialogs = new Dialog[1];
final boolean result[] = new boolean[1];
DialogDescriptor descriptor = new DialogDescriptor(
new WebStorePanel(false, path, new Runnable() {
@Override
public void run() {
InstallInfoReceiver receiver = new InstallInfoReceiver();
ExternalBrowserPlugin.getInstance().
addMessageListener(receiver);
try {
// #228605
openInProperBrowser(URI.create(PLUGIN_PAGE).toURL());
}
catch( MalformedURLException e ){
Logger.getLogger(ChromeExtensionManager.class.getName()).log(
Level.WARNING, null, e);
}
dialogs[0].setVisible(false);
dialogs[0].dispose();
result[0] = createReRun(currentStatus, extensionFile,
receiver);
}
private void openInProperBrowser(URL url) {
for (WebBrowser browser : WebBrowsers.getInstance().getAll(true, true, true)) {
if (browser.hasNetBeansIntegration()) {
// ignore it otherwise it will check the extension status and reopen just the same dialog
continue;
}
if (browser.getBrowserFamily() == getBrowserFamilyId()) {
browser.createNewBrowserPane().showURL(url);
return;
}
}
// fallback
HtmlBrowser.URLDisplayer.getDefault().showURL(url);
}
}, new Runnable() {
@Override
public void run() {
dialogs[0].setVisible(false);
dialogs[0].dispose();
result[0] = manualInstallPluginDialog(currentStatus,
extensionFile);
}
}),
NbBundle.getMessage(ChromeExtensionManager.class,
"TTL_InstallExtension"), true,
new Object[]{DialogDescriptor.CANCEL_OPTION},
DialogDescriptor.CANCEL_OPTION,
DialogDescriptor.DEFAULT_ALIGN, null, null);
Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor);
dialogs[0] = dialog;
dialog.setVisible(true);
return result[0];
}
private boolean createReRun(final
ExtensionManager.ExtensitionStatus currentStatus,
final File extensionFile, final InstallInfoReceiver receiver)
{
final Dialog[] dialogs = new Dialog[1];
final boolean result[] = new boolean[1];
DialogDescriptor descriptor = new DialogDescriptor(
new WebStorePanel(true, null, new Runnable() {
@Override
public void run() {
ExtensitionStatus status = isInstalled();
if ( receiver.isInstalled() ||
status== ExtensitionStatus.INSTALLED)
{
result[0] = true;
dialogs[0].setVisible(false);
dialogs[0].dispose();
}
}
}, new Runnable() {
@Override
public void run() {
dialogs[0].setVisible(false);
dialogs[0].dispose();
result[0] = manualInstallPluginDialog(currentStatus, extensionFile);
}
}),
NbBundle.getMessage(ChromeExtensionManager.class,
"TTL_InstallExtension"), true,
new Object[]{DialogDescriptor.CANCEL_OPTION},
DialogDescriptor.CANCEL_OPTION,
DialogDescriptor.DEFAULT_ALIGN, null, null);
Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor);
dialogs[0] = dialog;
dialog.setVisible(true);
return result[0];
}
private boolean alertGoogleWebStoreUpdate(
final ExtensionManager.ExtensitionStatus currentStatus)
{
final Dialog[] dialogs = new Dialog[1];
final boolean[] result = new boolean[1];
DialogDescriptor descriptor = new DialogDescriptor(
new WebStorePanel( new Runnable() {
@Override
public void run() {
ExtensitionStatus status = isInstalled();
if ( status!= ExtensitionStatus.INSTALLED){
return;
}
result[0] = true;
dialogs[0].setVisible(false);
dialogs[0].dispose();
}
} ),
NbBundle.getMessage(ChromeExtensionManager.class, "TTL_UpdateExtension"),
true,
new Object[]{DialogDescriptor.CANCEL_OPTION},
DialogDescriptor.CANCEL_OPTION,
DialogDescriptor.DEFAULT_ALIGN, null, null);
Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor);
dialogs[0] = dialog;
dialog.setVisible( true);
return result[0];
}
private static class FileFinder implements FileFilter {
FileFinder(String name){
this( name, false );
}
FileFinder(String name , boolean caseSensitive ){
myName = name;
isCaseSensitive = caseSensitive;
}
/* (non-Javadoc)
* @see java.io.FileFilter#accept(java.io.File)
*/
@Override
public boolean accept( File file ) {
if ( isCaseSensitive ){
return file.getName().equals( myName);
}
else {
return file.getName().toLowerCase(Locale.US).equals( myName);
}
}
private String myName;
private boolean isCaseSensitive;
}
}
static class InstallInfoReceiver implements MessageListener {
/* (non-Javadoc)
* @see org.netbeans.modules.extbrowser.plugins.MessageListener#messageReceived(org.netbeans.modules.extbrowser.plugins.Message)
*/
@Override
public void messageReceived( Message message ) {
if ( message.getType()==MessageType.READY){
isInstalled = true;
}
}
public boolean isInstalled(){
return isInstalled;
}
private volatile boolean isInstalled;
}
}