From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Sat, 30 Apr 2016 04:19:14 -0400 Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates --- src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java | 207 ++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 146 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java index baa892c..ab2756d 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java @@ -1,17 +1,19 @@ -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed 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. - +/* + * Copyright (C) 2009 The Android Open Source Project + * Copyright 2014 gitblit.com. + * + * Licensed 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 com.gitblit.transport.ssh.commands; import java.io.BufferedWriter; @@ -37,8 +39,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.gitblit.transport.ssh.SshCommandContext; -import com.gitblit.utils.IdGenerator; +import com.gitblit.Keys; +import com.gitblit.utils.StringUtils; import com.gitblit.utils.WorkQueue; import com.gitblit.utils.WorkQueue.CancelableRunnable; import com.gitblit.utils.cli.CmdLineParser; @@ -49,8 +51,13 @@ private static final Logger log = LoggerFactory.getLogger(BaseCommand.class); - /** Ssh context */ - protected SshCommandContext ctx; + private static final int PRIVATE_STATUS = 1 << 30; + + public final static int STATUS_CANCEL = PRIVATE_STATUS | 1; + + public final static int STATUS_NOT_FOUND = PRIVATE_STATUS | 2; + + public final static int STATUS_NOT_ADMIN = PRIVATE_STATUS | 3; protected InputStream in; @@ -62,6 +69,9 @@ protected ServerSession session; + /** Ssh command context */ + private SshCommandContext ctx; + /** Text of the command line which lead up to invoking this instance. */ private String commandName = ""; @@ -71,13 +81,10 @@ /** The task, as scheduled on a worker thread. */ private final AtomicReference<Future<?>> task; - private final WorkQueue.Executor executor; + private WorkQueue workQueue; public BaseCommand() { task = Atomics.newReference(); - IdGenerator gen = new IdGenerator(); - WorkQueue w = new WorkQueue(gen); - this.executor = w.getDefaultQueue(); } @Override @@ -87,6 +94,13 @@ @Override public void destroy() { + log.debug("destroying " + getClass().getName()); + Future<?> future = task.getAndSet(null); + if (future != null && !future.isDone()) { + future.cancel(true); + } + session = null; + ctx = null; } protected static PrintWriter toPrintWriter(final OutputStream o) { @@ -96,8 +110,29 @@ @Override public abstract void start(Environment env) throws IOException; + protected void provideStateTo(final BaseCommand cmd) { + cmd.setContext(ctx); + cmd.setWorkQueue(workQueue); + cmd.setInputStream(in); + cmd.setOutputStream(out); + cmd.setErrorStream(err); + cmd.setExitCallback(exit); + } + + public WorkQueue getWorkQueue() { + return workQueue; + } + + public void setWorkQueue(WorkQueue workQueue) { + this.workQueue = workQueue; + } + public void setContext(SshCommandContext ctx) { this.ctx = ctx; + } + + public SshCommandContext getContext() { + return ctx; } @Override @@ -118,16 +153,6 @@ @Override public void setExitCallback(final ExitCallback callback) { this.exit = callback; - } - - protected void provideBaseStateTo(final Command cmd) { - if (cmd instanceof BaseCommand) { - ((BaseCommand) cmd).setContext(ctx); - } - cmd.setInputStream(in); - cmd.setOutputStream(out); - cmd.setErrorStream(err); - cmd.setExitCallback(exit); } protected String getName() { @@ -188,9 +213,44 @@ } if (clp.wasHelpRequestedByOption()) { + CommandMetaData meta = getClass().getAnnotation(CommandMetaData.class); + String title = meta.name().toUpperCase() + ": " + meta.description(); + String b = com.gitblit.utils.StringUtils.leftPad("", title.length() + 2, '═'); StringWriter msg = new StringWriter(); - clp.printDetailedUsage(commandName, msg); - msg.write(usage()); + msg.write('\n'); + msg.write(b); + msg.write('\n'); + msg.write(' '); + msg.write(title); + msg.write('\n'); + msg.write(b); + msg.write("\n\n"); + msg.write("USAGE\n"); + msg.write("─────\n"); + msg.write(' '); + msg.write(commandName); + msg.write('\n'); + msg.write(" "); + clp.printSingleLineUsage(msg, null); + msg.write("\n\n"); + String txt = getUsageText(); + if (!StringUtils.isEmpty(txt)) { + msg.write(txt); + msg.write("\n\n"); + } + msg.write("ARGUMENTS & OPTIONS\n"); + msg.write("───────────────────\n"); + clp.printUsage(msg, null); + msg.write('\n'); + String examples = usage().trim(); + if (!StringUtils.isEmpty(examples)) { + msg.write('\n'); + msg.write("EXAMPLES\n"); + msg.write("────────\n"); + msg.write(examples); + msg.write('\n'); + } + throw new UnloggedFailure(1, msg.toString()); } } @@ -200,8 +260,41 @@ return new CmdLineParser(options); } - protected String usage() { + public String usage() { + Class<? extends BaseCommand> clazz = getClass(); + if (clazz.isAnnotationPresent(UsageExamples.class)) { + return examples(clazz.getAnnotation(UsageExamples.class).examples()); + } else if (clazz.isAnnotationPresent(UsageExample.class)) { + return examples(clazz.getAnnotation(UsageExample.class)); + } return ""; + } + + protected String getUsageText() { + return ""; + } + + protected String examples(UsageExample... examples) { + int sshPort = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 29418); + String username = getContext().getClient().getUsername(); + String hostname = "localhost"; + String ssh = String.format("ssh -l %s -p %d %s", username, sshPort, hostname); + + StringBuilder sb = new StringBuilder(); + for (UsageExample example : examples) { + sb.append(example.description()).append("\n\n"); + String syntax = example.syntax(); + syntax = syntax.replace("${ssh}", ssh); + syntax = syntax.replace("${username}", username); + syntax = syntax.replace("${cmd}", commandName); + sb.append(" ").append(syntax).append("\n\n"); + } + return sb.toString(); + } + + protected void showHelp() throws UnloggedFailure { + argv = new String [] { "--help" }; + parseCommandLine(); } private final class TaskThunk implements CancelableRunnable { @@ -220,7 +313,7 @@ public void cancel() { synchronized (this) { try { - // onExit(/*STATUS_CANCEL*/); + onExit(STATUS_CANCEL); } finally { ctx = null; } @@ -266,13 +359,13 @@ } /** Runnable function which can throw an exception. */ - public static interface CommandRunnable { - public void run() throws Exception; + public interface CommandRunnable { + void run() throws Exception; } /** Runnable function which can retrieve a project name related to the task */ - public static interface RepositoryCommandRunnable extends CommandRunnable { - public String getRepository(); + public interface RepositoryCommandRunnable extends CommandRunnable { + String getRepository(); } /** @@ -305,23 +398,17 @@ /** * Terminate this command and return a result code to the remote client. * <p> - * Commands should invoke this at most once. Once invoked, the command may - * lose access to request based resources as any callbacks previously - * registered with {@link RequestCleanup} will fire. + * Commands should invoke this at most once. * - * @param rc - * exit code for the remote client. + * @param rc exit code for the remote client. */ protected void onExit(final int rc) { exit.onExit(rc); - // if (cleanup != null) { - // cleanup.run(); - // } } private int handleError(final Throwable e) { - if ((e.getClass() == IOException.class && "Pipe closed".equals(e.getMessage())) || // - (e.getClass() == SshException.class && "Already closed".equals(e.getMessage())) || // + if ((e.getClass() == IOException.class && "Pipe closed".equals(e.getMessage())) || + (e.getClass() == SshException.class && "Already closed".equals(e.getMessage())) || e.getClass() == InterruptedIOException.class) { // This is sshd telling us the client just dropped off while // we were waiting for a read or a write to complete. Either @@ -334,16 +421,14 @@ } else { final StringBuilder m = new StringBuilder(); m.append("Internal server error"); - // if (userProvider.get().isIdentifiedUser()) { - // final IdentifiedUser u = (IdentifiedUser) userProvider.get(); - // m.append(" (user "); - // m.append(u.getAccount().getUserName()); - // m.append(" account "); - // m.append(u.getAccountId()); - // m.append(")"); - // } - // m.append(" during "); - // m.append(contextProvider.get().getCommandLine()); + String user = ctx.getClient().getUsername(); + if (user != null) { + m.append(" (user "); + m.append(user); + m.append(")"); + } + m.append(" during "); + m.append(ctx.getCommandLine()); log.error(m.toString(), e); } @@ -393,7 +478,7 @@ */ protected void startThread(final CommandRunnable thunk) { final TaskThunk tt = new TaskThunk(thunk); - task.set(executor.submit(tt)); + task.set(workQueue.getDefaultQueue().submit(tt)); } /** Thrown from {@link CommandRunnable#run()} with client message and code. */ -- Gitblit v1.9.1