1 package ch
.cyberduck
.core
;
4 * Copyright (c) 2009 David Kocher. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to:
18 * dkocher@cyberduck.ch
21 import ch
.cyberduck
.core
.exception
.BackgroundException
;
22 import ch
.cyberduck
.core
.exception
.ConnectionCanceledException
;
23 import ch
.cyberduck
.core
.threading
.BackgroundAction
;
24 import ch
.cyberduck
.core
.threading
.BackgroundActionRegistry
;
25 import ch
.cyberduck
.core
.threading
.ControllerMainAction
;
26 import ch
.cyberduck
.core
.threading
.LoggingUncaughtExceptionHandler
;
27 import ch
.cyberduck
.core
.threading
.MainAction
;
28 import ch
.cyberduck
.core
.threading
.ThreadPool
;
30 import org
.apache
.commons
.lang3
.concurrent
.ConcurrentUtils
;
31 import org
.apache
.log4j
.Logger
;
33 import java
.util
.concurrent
.Callable
;
34 import java
.util
.concurrent
.Future
;
35 import java
.util
.concurrent
.RejectedExecutionException
;
40 public abstract class AbstractController
implements Controller
{
41 private static final Logger log
= Logger
.getLogger(AbstractController
.class);
43 private ThreadPool singleExecutor
;
45 private ThreadPool concurrentExecutor
;
47 protected AbstractController() {
48 this(new LoggingUncaughtExceptionHandler());
51 protected AbstractController(final Thread
.UncaughtExceptionHandler handler
) {
52 singleExecutor
= new ThreadPool(1, handler
);
53 concurrentExecutor
= new ThreadPool(handler
);
57 * Does wait for main action to return before continuing the caller thread.
59 * @param runnable The action to execute
62 public void invoke(final MainAction runnable
) {
63 this.invoke(runnable
, false);
67 * List of pending background tasks or this browser
69 private BackgroundActionRegistry registry
70 = new BackgroundActionRegistry();
73 * Pending background actions
75 * @return List of tasks.
77 public BackgroundActionRegistry
getActions() {
82 * @return true if there is any network activity running in the background
84 public boolean isActivityRunning() {
85 final BackgroundAction current
= this.getActions().getCurrent();
86 return null != current
;
90 * Will queue up the <code>BackgroundAction</code> to be run in a background thread. Will be executed
91 * as soon as no other previous <code>BackgroundAction</code> is pending.
92 * Will return immediately but not run the runnable before the lock of the runnable is acquired.
94 * @param action The runnable to execute in a secondary Thread
95 * @see java.lang.Thread
96 * @see ch.cyberduck.core.threading.BackgroundAction#lock()
99 public <T
> Future
<T
> background(final BackgroundAction
<T
> action
) {
100 if(log
.isDebugEnabled()) {
101 log
.debug(String
.format("Run action %s in background", action
));
103 if(registry
.contains(action
)) {
104 log
.warn(String
.format("Skip duplicate background action %s found in registry", action
));
107 registry
.add(action
);
109 // Start background task
110 final Callable
<T
> command
= new BackgroundCallable
<T
>(action
);
112 final Future
<T
> task
;
113 if(null == action
.lock()) {
114 task
= concurrentExecutor
.execute(command
);
117 if(log
.isDebugEnabled()) {
118 log
.debug(String
.format("Synchronize on lock %s for action %s", action
.lock(), action
));
120 task
= singleExecutor
.execute(command
);
122 if(log
.isInfoEnabled()) {
123 log
.info(String
.format("Scheduled background runnable %s for execution", action
));
127 catch(RejectedExecutionException e
) {
128 log
.error(String
.format("Error scheduling background task %s for execution. %s", action
, e
.getMessage()));
130 return ConcurrentUtils
.constantFuture(null);
134 protected void invalidate() {
135 if(log
.isInfoEnabled()) {
136 log
.info(String
.format("Terminating single executor thread pool %s", singleExecutor
));
138 singleExecutor
.shutdown(false);
139 if(log
.isInfoEnabled()) {
140 log
.info(String
.format("Terminating concurrent executor thread pool %s", concurrentExecutor
));
142 concurrentExecutor
.shutdown(false);
145 private final class BackgroundCallable
<T
> implements Callable
<T
> {
146 private final BackgroundAction
<T
> action
;
149 * Keep client stacktrace
151 private final Exception client
= new Exception();
153 public BackgroundCallable(final BackgroundAction
<T
> action
) {
154 this.action
= action
;
159 if(log
.isDebugEnabled()) {
160 log
.debug(String
.format("Acquired lock for background runnable %s", action
));
162 if(action
.isCanceled()) {
163 // Canceled action yields no result
167 if(log
.isDebugEnabled()) {
168 log
.debug(String
.format("Prepare background action %s", action
));
171 // Execute the action of the runnable
172 if(log
.isDebugEnabled()) {
173 log
.debug(String
.format("Call background action %s", action
));
175 return action
.call();
177 catch(ConnectionCanceledException e
) {
178 log
.warn(String
.format("Connection canceled for background task %s", action
));
188 registry
.remove(action
);
190 // If there was any failure, display the summary now
192 if(log
.isDebugEnabled()) {
193 log
.debug(String
.format("Retry background action %s", action
));
199 if(log
.isDebugEnabled()) {
200 log
.debug(String
.format("Invoke cleanup for background action %s", action
));
202 // Invoke the cleanup on the main thread to let the action synchronize the user interface
203 invoke(new ControllerMainAction(AbstractController
.this) {
210 log
.error(String
.format("Exception running cleanup task %s", e
.getMessage()), e
);
214 if(log
.isDebugEnabled()) {
215 log
.debug(String
.format("Releasing lock for background runnable %s", action
));
219 // Canceled action yields no result
224 protected void failure(final Exception trace
, final Exception failure
) {
225 trace
.initCause(failure
);
226 log
.error(String
.format("Unhandled exception running background task %s", failure
.getMessage()), trace
);
230 public void start(final BackgroundAction action
) {
231 if(log
.isDebugEnabled()) {
232 log
.debug(String
.format("Start action %s", action
));
237 public void cancel(final BackgroundAction action
) {
238 if(log
.isDebugEnabled()) {
239 log
.debug(String
.format("Cancel action %s", action
));
244 public void stop(final BackgroundAction action
) {
245 if(log
.isDebugEnabled()) {
246 log
.debug(String
.format("Stop action %s", action
));
251 public void message(final String message
) {
256 public void log(final boolean request
, final String message
) {
261 public boolean alert(final Host host
, final BackgroundException failure
, final StringBuilder transcript
) {
262 log
.warn(failure
.getMessage(), failure
);