blob: 3664cb26e0604fb8b16f831effd8952025960b52 [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.autoupdate.cli;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import org.netbeans.api.autoupdate.*;
import org.netbeans.api.autoupdate.InstallSupport.Installer;
import org.netbeans.api.autoupdate.InstallSupport.Validator;
import org.netbeans.api.autoupdate.OperationContainer.OperationInfo;
import org.netbeans.api.autoupdate.OperationSupport.Restarter;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.sendopts.CommandException;
import org.netbeans.spi.sendopts.Env;
import org.netbeans.spi.sendopts.Option;
import org.netbeans.spi.sendopts.OptionGroups;
import org.netbeans.spi.sendopts.OptionProcessor;
import org.openide.util.*;
import static org.netbeans.modules.autoupdate.cli.Bundle.*;
import org.netbeans.modules.progress.spi.Controller;
import org.netbeans.modules.progress.spi.InternalHandle;
import org.netbeans.modules.progress.spi.ProgressEvent;
import org.netbeans.modules.progress.spi.ProgressUIWorker;
/**
*
* @author Jaroslav Tulach, Jiri Rechtacek
*/
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.sendopts.OptionProcessor.class)
public class ModuleOptions extends OptionProcessor {
private static final Logger LOG = Logger.getLogger(ModuleOptions.class.getName());
private Option list;
private Option install;
private Option disable;
private Option enable;
private Option update;
private Option refresh;
private Option updateAll;
private Option both;
private Option extraUC;
private Option directDisable;
private final Collection<UpdateUnitProvider> ownUUP = new HashSet<> ();
/** Creates a new instance of ModuleOptions */
public ModuleOptions() {
}
@NbBundle.Messages({
"MSG_UpdateModules=Updates all or specified modules",
"MSG_UpdateAll=Updates all modules",
"MSG_Refresh=Refresh all catalogs",
"MSG_ExtraUC=Add a extra Update Center (URL)"
})
private Option init() {
if (both != null) {
return both;
}
String b = "org.netbeans.modules.autoupdate.cli.Bundle";
list = Option.shortDescription(
Option.withoutArgument(Option.NO_SHORT_NAME, "list"), b, "MSG_ListModules"); // NOI18N
install = Option.shortDescription(
Option.additionalArguments(Option.NO_SHORT_NAME, "install"), b, "MSG_InstallModules"); // NOI18N
disable = Option.shortDescription(
Option.additionalArguments(Option.NO_SHORT_NAME, "disable"), b, "MSG_DisableModules"); // NOI18N
enable = Option.shortDescription(
Option.additionalArguments(Option.NO_SHORT_NAME, "enable"), b, "MSG_EnableModules"); // NOI18N
update = Option.shortDescription(
Option.additionalArguments(Option.NO_SHORT_NAME, "update"), b, "MSG_UpdateModules"); // NOI18N
refresh = Option.shortDescription(
Option.withoutArgument(Option.NO_SHORT_NAME, "refresh"), b, "MSG_Refresh"); // NOI18N
updateAll = Option.shortDescription(
Option.withoutArgument(Option.NO_SHORT_NAME, "update-all"), b, "MSG_UpdateAll"); // NOI18N
extraUC = Option.shortDescription(
Option.requiredArgument(Option.NO_SHORT_NAME, "extra-uc"), b, "MSG_ExtraUC"); // NOI18N
directDisable = Option.shortDescription(
Option.additionalArguments(Option.NO_SHORT_NAME, "direct-disable"), b, "MSG_DirectDisableModule"); // NOI18N
Option oper = OptionGroups.someOf(refresh, list, install, disable, enable, update, updateAll, extraUC, directDisable);
Option modules = Option.withoutArgument(Option.NO_SHORT_NAME, "modules");
both = OptionGroups.allOf(modules, oper);
return both;
}
@Override
public Set<Option> getOptions() {
return Collections.singleton(init());
}
@NbBundle.Messages({
"# {0} - update center name",
"MSG_RefreshingUpdateCenter=Refreshing {0}"
})
private void refresh(Env env) throws CommandException {
for (UpdateUnitProvider p : UpdateUnitProviderFactory.getDefault().getUpdateUnitProviders(true)) {
try {
env.getOutputStream().println(MSG_RefreshingUpdateCenter(p.getDisplayName()));
p.refresh(null, true);
} catch (IOException ex) {
throw (CommandException)new CommandException(31, ex.getMessage()).initCause(ex);
}
}
}
@NbBundle.Messages({
"MSG_ListHeader_CodeName=Code Name",
"MSG_ListHeader_Version=Version",
"MSG_ListHeader_State=State"
})
private void listAllModules(PrintStream out) throws IOException {
List<UpdateUnit> modules = UpdateManager.getDefault().getUpdateUnits();
PrintTable table = new PrintTable(
MSG_ListHeader_CodeName(), MSG_ListHeader_Version(), MSG_ListHeader_State()
);
table.setLimits(50, -1, -1);
for (UpdateUnit uu : modules) {
table.addRow(Status.toArray(uu));
}
StringBuilder sb = new StringBuilder();
table.write(sb);
out.print(sb.toString());
out.flush();
}
private static <T extends Throwable> T initCause(T t, Throwable cause) {
t.initCause(cause);
return t;
}
@Override
protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
try {
try {
if (optionValues.containsKey(extraUC)) {
extraUC(env, optionValues.get(extraUC));
}
if (optionValues.containsKey(refresh)) {
refresh(env);
}
if (optionValues.containsKey(list)) {
listAllModules(env.getOutputStream());
}
if (optionValues.containsKey(install)) {
install(env, optionValues.get(install));
}
if (optionValues.containsKey(disable)) {
changeModuleState(env, optionValues.get(disable), false, false);
}
if (optionValues.containsKey(enable)) {
changeModuleState(env, optionValues.get(enable), true, false);
}
if (optionValues.containsKey(directDisable)) {
changeModuleState(env, optionValues.get(directDisable), false, true);
}
} catch (InterruptedException | IOException | OperationException ex) {
throw initCause(new CommandException(4), ex);
}
if (optionValues.containsKey(updateAll)) {
updateAll(env);
}
if (optionValues.containsKey(update)) {
updateModules(env, optionValues.get(update));
}
} finally {
for (UpdateUnitProvider uuc : ownUUP) {
UpdateUnitProviderFactory.getDefault().remove(uuc);
}
}
}
@NbBundle.Messages({
"# {0} - name of the module",
"MSG_FoundButNotInstalled=Found {0}, but not installed",
"# {0} - requested patterns",
"MSG_CannotFindAnyPattern=Cannot find any module matching {0}\n"
})
private void changeModuleState(Env env, String[] cnbs, boolean enable, boolean direct) throws IOException, CommandException, InterruptedException, OperationException {
CodeNameMatcher cnm = CodeNameMatcher.create(env, cnbs);
List<UpdateUnit> units = UpdateManager.getDefault().getUpdateUnits();
OperationContainer<OperationSupport> operate =
enable ? OperationContainer.createForEnable() :
(direct ? OperationContainer.createForDirectDisable() : OperationContainer.createForDisable());
StringBuilder sb = new StringBuilder();
boolean found = false;
for (UpdateUnit updateUnit : units) {
final String codeName = updateUnit.getCodeName();
if (cnm.matches(codeName)) {
final UpdateElement elem = updateUnit.getInstalled();
if (elem != null) {
found = true;
if (elem.isEnabled() != enable) {
operate.add(updateUnit, elem);
}
} else {
sb.append("\n").append(MSG_FoundButNotInstalled(codeName));
}
}
}
if (!found) {
sb.insert(0, MSG_CannotFindAnyPattern(cnm));
throw new CommandException(55, sb.toString());
}
OperationSupport support = operate.getSupport();
if (support != null) {
support.doOperation(null);
}
}
@NbBundle.Messages({
"MSG_UpdateNotFound=Updates not found.",
"# {0} - pattern",
"MSG_UpdateNoMatchPattern=Nothing to update. The pattern {0} has no match among available updates.",
"# {0} - module name",
"# {1} - installed version",
"# {2} - available version",
"MSG_Update=Will update {0}@{1} to version {2}",
"# {0} - plugin name",
"MSG_Download=Downloading {0}"
})
private void updateModules(final Env env, String... pattern) throws CommandException {
if (! initialized()) {
refresh(env);
}
CodeNameMatcher cnm = CodeNameMatcher.create(env, pattern);
List<UpdateUnit> units = UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.MODULE);
final Collection <String> firstClass = getFirstClassModules();
boolean firstClassHasUpdates = false;
OperationContainer<InstallSupport> operate = OperationContainer.createForUpdate();
if (! firstClass.isEmpty() && pattern.length == 0) {
for (UpdateUnit uu : units) {
if (uu.getInstalled() == null) {
continue;
}
final List<UpdateElement> updates = uu.getAvailableUpdates();
if (updates.isEmpty()) {
continue;
}
if (! firstClass.contains (uu.getCodeName ())) {
continue;
}
final UpdateElement ue = updates.get(0);
env.getOutputStream().println(
Bundle.MSG_Update(uu.getCodeName(), uu.getInstalled().getSpecificationVersion(), ue.getSpecificationVersion()
));
if (operate.canBeAdded(uu, ue)) {
LOG.log(Level.FINE, " ... update {0} -> {1}", new Object[]{uu.getInstalled(), ue});
firstClassHasUpdates = true;
OperationInfo<InstallSupport> info = operate.add(ue);
if (info != null) {
Set<UpdateElement> requiredElements = info.getRequiredElements();
LOG.log(Level.FINE, " ... add required elements: {0}", requiredElements);
operate.add(requiredElements);
}
}
}
}
if (! firstClassHasUpdates) {
for (UpdateUnit uu : units) {
if (uu.getInstalled() == null) {
continue;
}
final List<UpdateElement> updates = uu.getAvailableUpdates();
if (updates.isEmpty()) {
continue;
}
if (pattern.length > 0 && !cnm.matches(uu.getCodeName())) {
continue;
}
final UpdateElement ue = updates.get(0);
env.getOutputStream().println(
Bundle.MSG_Update(uu.getCodeName(), uu.getInstalled().getSpecificationVersion(), ue.getSpecificationVersion()
));
if (operate.canBeAdded(uu, ue)) {
LOG.log(Level.FINE, " ... update {0} -> {1}", new Object[]{uu.getInstalled(), ue});
OperationInfo<InstallSupport> info = operate.add(ue);
if (info != null) {
Set<UpdateElement> requiredElements = info.getRequiredElements();
LOG.log(Level.FINE, " ... add required elements: {0}", requiredElements);
operate.add(requiredElements);
}
}
}
}
final InstallSupport support = operate.getSupport();
if (support == null) {
env.getOutputStream().println(cnm.isEmpty() ? Bundle.MSG_UpdateNotFound() : Bundle.MSG_UpdateNoMatchPattern(cnm));
env.getOutputStream().println("updates=0"); // NOI18N
return;
}
env.getOutputStream().println("updates=" + operate.listAll().size()); // NOI18N
ProgressHandle downloadHandle = new CLIInternalHandle("downloading-updates", env).createProgressHandle(); // NOI18N
downloadHandle.setInitialDelay(0);
try {
final Validator res1 = support.doDownload(downloadHandle, null, false);
Installer res2 = support.doValidate(res1, null);
ProgressHandle installHandle = new CLIInternalHandle("installing-updates", env).createProgressHandle(); // NOI18N
installHandle.setInitialDelay(0);
Restarter res3 = support.doInstall(res2, installHandle);
if (res3 != null) {
support.doRestart(res3, null);
}
} catch (OperationException ex) {
try {
support.doCancel();
throw (CommandException)new CommandException(33, ex.getMessage()).initCause(ex);
} catch (OperationException ex1) {
throw (CommandException)new CommandException(33, ex1.getMessage()).initCause(ex1);
}
}
}
@NbBundle.Messages({
"# {0} - module name",
"# {1} - module version",
"MSG_Installing=Installing {0}@{1}",
"# {0} - module name",
"MSG_AlreadyPresent=Module {0} is already installed.",
})
private void install(final Env env, String... pattern) throws CommandException {
if (! initialized()) {
refresh(env);
}
CodeNameMatcher cnm = CodeNameMatcher.create(env, pattern);
List<UpdateUnit> units = UpdateManager.getDefault().getUpdateUnits();
OperationContainer<InstallSupport> operate = OperationContainer.createForInstall();
boolean found = false;
StringBuilder sb = new StringBuilder();
for (UpdateUnit uu : units) {
if (!cnm.matches(uu.getCodeName())) {
continue;
}
found = true;
if (uu.getInstalled() != null) {
sb.append(MSG_AlreadyPresent(uu.getCodeName())).append("\n");
continue;
}
if (uu.getAvailableUpdates().isEmpty()) {
continue;
}
UpdateElement ue = uu.getAvailableUpdates().get(0);
env.getOutputStream().println(
Bundle.MSG_Installing(uu.getCodeName(), ue.getSpecificationVersion()));
operate.add(ue);
}
final InstallSupport support = operate.getSupport();
if (support == null) {
if (!found) {
sb.insert(0, MSG_CannotFindAnyPattern(cnm));
throw new CommandException(55, sb.toString());
}
env.getOutputStream().print(sb.toString());
return;
}
try {
env.getOutputStream().println("modules=" + operate.listAll().size()); // NOI18N
ProgressHandle downloadHandle = new CLIInternalHandle("downloading-modules", env).createProgressHandle(); // NOI18N
downloadHandle.setInitialDelay(0);
final Validator res1 = support.doDownload(downloadHandle, null, false);
Installer res2 = support.doValidate(res1, null);
ProgressHandle installHandle = new CLIInternalHandle("installing-modules", env).createProgressHandle(); // NOI18N
installHandle.setInitialDelay(0);
Restarter res3 = support.doInstall(res2, installHandle);
if (res3 != null) {
support.doRestart(res3, null);
}
} catch (OperationException ex) {
// a hack
if (OperationException.ERROR_TYPE.INSTALL.equals(ex.getErrorType())) {
// probably timeout of loading
env.getErrorStream().println(ex.getLocalizedMessage());
throw (CommandException) new CommandException(34, ex.getMessage()).initCause(ex);
} else {
try {
support.doCancel();
throw (CommandException) new CommandException(32, ex.getMessage()).initCause(ex);
} catch (OperationException ex1) {
throw (CommandException) new CommandException(32, ex1.getMessage()).initCause(ex1);
}
}
}
}
private void updateAll(Env env) throws CommandException {
updateModules(env);
}
@NbBundle.Messages({
"MSG_NoURL=None extra Update Center (URL) specified."
})
private void extraUC(Env env, String... urls) throws CommandException {
List<URL> url2UC = new ArrayList<> (urls.length);
for (String spec : urls) {
try {
url2UC.add(new URL(spec));
} catch (MalformedURLException ex) {
throw initCause(new CommandException(4), ex);
}
}
for (URL url : url2UC) {
ownUUP.add(UpdateUnitProviderFactory.getDefault().create(Long.toString(System.currentTimeMillis()), url.toExternalForm(), url));
}
refresh(env);
}
private boolean initialized() {
Preferences pref = NbPreferences.root ().node ("/org/netbeans/modules/autoupdate");
long last = pref.getLong("lastCheckTime", -1);
return last != -1;
}
private static final String PLUGIN_MANAGER_FIRST_CLASS_MODULES = "plugin.manager.first.class.modules"; // NOI18N
private Collection<String> getFirstClassModules() {
Preferences p = NbPreferences.root().node("/org/netbeans/modules/autoupdate"); // NOI18N
String names = p.get(PLUGIN_MANAGER_FIRST_CLASS_MODULES, "");
Set<String> res = new HashSet<> ();
StringTokenizer en = new StringTokenizer (names, ","); // NOI18N
while (en.hasMoreTokens ()) {
res.add (en.nextToken ().trim ());
}
return res;
}
private class CLIInternalHandle extends InternalHandle {
public CLIInternalHandle(String displayName, Env env) {
super(displayName, null, false);
setController(new Controller(new CLIProgressUIWorker(env)));
}
}
private class CLIProgressUIWorker implements ProgressUIWorker {
private final Env env;
public CLIProgressUIWorker(Env env) {
this.env = env;
}
@Override
public void processProgressEvent(ProgressEvent event) {
printEvent(event);
}
@Override
public void processSelectedProgressEvent(ProgressEvent event) {
printEvent(event);
}
private void printEvent(ProgressEvent event) {
final String msg = event.getMessage();
if (msg != null && msg.length() > 0) {
env.getOutputStream().println(msg);
}
}
}
}