blob: 3b8ba30d22882487f8b044a0b403869cc460dba6 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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.intellij.tasks.impl;
import com.intellij.notification.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsTaskHandler;
import com.intellij.openapi.vcs.VcsType;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.tasks.*;
import com.intellij.tasks.actions.TaskSearchSupport;
import com.intellij.tasks.config.TaskRepositoriesConfigurable;
import com.intellij.tasks.context.WorkingContextManager;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.util.ArrayUtil;
import com.intellij.util.EventDispatcher;
import com.intellij.util.Function;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.xmlb.XmlSerializationException;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.AbstractCollection;
import com.intellij.util.xmlb.annotations.Property;
import com.intellij.util.xmlb.annotations.Tag;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.Timer;
import javax.swing.event.HyperlinkEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author Dmitry Avdeev
*/
@State(
name = "TaskManager",
storages = {
@Storage(file = StoragePathMacros.WORKSPACE_FILE)
}
)
public class TaskManagerImpl extends TaskManager implements ProjectComponent, PersistentStateComponent<TaskManagerImpl.Config>,
ChangeListDecorator {
private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.impl.TaskManagerImpl");
private static final DecimalFormat LOCAL_TASK_ID_FORMAT = new DecimalFormat("LOCAL-00000");
public static final Comparator<Task> TASK_UPDATE_COMPARATOR = new Comparator<Task>() {
public int compare(Task o1, Task o2) {
int i = Comparing.compare(o2.getUpdated(), o1.getUpdated());
return i == 0 ? Comparing.compare(o2.getCreated(), o1.getCreated()) : i;
}
};
private static final Convertor<Task, String> KEY_CONVERTOR = new Convertor<Task, String>() {
@Override
public String convert(Task o) {
return o.getId();
}
};
static final String TASKS_NOTIFICATION_GROUP = "Task Group";
private final Project myProject;
private final WorkingContextManager myContextManager;
private final Map<String, Task> myIssueCache = Collections.synchronizedMap(new LinkedHashMap<String, Task>());
private final Map<String, LocalTask> myTasks = Collections.synchronizedMap(new LinkedHashMap<String, LocalTask>() {
@Override
public LocalTask put(String key, LocalTask task) {
LocalTask result = super.put(key, task);
if (size() > myConfig.taskHistoryLength) {
ArrayList<LocalTask> list = new ArrayList<LocalTask>(values());
Collections.sort(list, TASK_UPDATE_COMPARATOR);
for (LocalTask oldest : list) {
if (!oldest.isDefault()) {
remove(oldest);
break;
}
}
}
return result;
}
});
@NotNull
private LocalTask myActiveTask = createDefaultTask();
private Timer myCacheRefreshTimer;
private volatile boolean myUpdating;
private final Config myConfig = new Config();
private final ChangeListAdapter myChangeListListener;
private final ChangeListManager myChangeListManager;
private final List<TaskRepository> myRepositories = new ArrayList<TaskRepository>();
private final EventDispatcher<TaskListener> myDispatcher = EventDispatcher.create(TaskListener.class);
private Set<TaskRepository> myBadRepositories = new ConcurrentHashSet<TaskRepository>();
public TaskManagerImpl(Project project, WorkingContextManager contextManager, ChangeListManager changeListManager) {
myProject = project;
myContextManager = contextManager;
myChangeListManager = changeListManager;
myChangeListListener = new ChangeListAdapter() {
@Override
public void changeListRemoved(ChangeList list) {
LocalTask task = getAssociatedTask((LocalChangeList)list);
if (task != null) {
for (ChangeListInfo info : task.getChangeLists()) {
if (Comparing.equal(info.id, ((LocalChangeList)list).getId())) {
info.id = "";
}
}
}
}
@Override
public void defaultListChanged(ChangeList oldDefaultList, ChangeList newDefaultList) {
final LocalTask associatedTask = getAssociatedTask((LocalChangeList)newDefaultList);
if (associatedTask != null && !getActiveTask().equals(associatedTask)) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
activateTask(associatedTask, true);
}
}, myProject.getDisposed());
}
}
};
}
@Override
public TaskRepository[] getAllRepositories() {
return myRepositories.toArray(new TaskRepository[myRepositories.size()]);
}
public <T extends TaskRepository> void setRepositories(List<T> repositories) {
Set<TaskRepository> set = new HashSet<TaskRepository>(myRepositories);
set.removeAll(repositories);
myBadRepositories.removeAll(set); // remove all changed reps
myIssueCache.clear();
myRepositories.clear();
myRepositories.addAll(repositories);
reps:
for (T repository : repositories) {
if (repository.isShared() && repository.getUrl() != null) {
List<TaskProjectConfiguration.SharedServer> servers = getProjectConfiguration().servers;
TaskRepositoryType type = repository.getRepositoryType();
for (TaskProjectConfiguration.SharedServer server : servers) {
if (repository.getUrl().equals(server.url) && type.getName().equals(server.type)) {
continue reps;
}
}
TaskProjectConfiguration.SharedServer server = new TaskProjectConfiguration.SharedServer();
server.type = type.getName();
server.url = repository.getUrl();
servers.add(server);
}
}
}
@Override
public void removeTask(LocalTask task) {
if (task.isDefault()) return;
if (myActiveTask.equals(task)) {
activateTask(myTasks.get(LocalTaskImpl.DEFAULT_TASK_ID), true);
}
myTasks.remove(task.getId());
myDispatcher.getMulticaster().taskRemoved(task);
myContextManager.removeContext(task);
}
@Override
public void addTaskListener(TaskListener listener) {
myDispatcher.addListener(listener);
}
@Override
public void removeTaskListener(TaskListener listener) {
myDispatcher.removeListener(listener);
}
@NotNull
@Override
public LocalTask getActiveTask() {
return myActiveTask;
}
@Nullable
@Override
public LocalTask findTask(String id) {
return myTasks.get(id);
}
@NotNull
@Override
public List<Task> getIssues(@Nullable final String query) {
return getIssues(query, true);
}
@Override
public List<Task> getIssues(@Nullable final String query, final boolean forceRequest) {
return getIssues(query, 0, 50, true, new EmptyProgressIndicator(), forceRequest);
}
@Override
public List<Task> getIssues(@Nullable String query,
int offset,
int limit,
final boolean withClosed,
@NotNull ProgressIndicator indicator,
boolean forceRequest) {
List<Task> tasks = getIssuesFromRepositories(query, offset, limit, withClosed, forceRequest, indicator);
if (tasks == null) {
return getCachedIssues(withClosed);
}
myIssueCache.putAll(ContainerUtil.newMapFromValues(tasks.iterator(), KEY_CONVERTOR));
return ContainerUtil.filter(tasks, new Condition<Task>() {
@Override
public boolean value(final Task task) {
return withClosed || !task.isClosed();
}
});
}
@Override
public List<Task> getCachedIssues() {
return getCachedIssues(true);
}
@Override
public List<Task> getCachedIssues(final boolean withClosed) {
return ContainerUtil.filter(myIssueCache.values(), new Condition<Task>() {
@Override
public boolean value(final Task task) {
return withClosed || !task.isClosed();
}
});
}
@Nullable
@Override
public Task updateIssue(@NotNull String id) {
for (TaskRepository repository : getAllRepositories()) {
if (repository.extractId(id) == null) {
continue;
}
try {
Task issue = repository.findTask(id);
if (issue != null) {
LocalTask localTask = findTask(id);
if (localTask != null) {
localTask.updateFromIssue(issue);
return localTask;
}
return issue;
}
}
catch (Exception e) {
LOG.info(e);
}
}
return null;
}
@Override
public List<LocalTask> getLocalTasks() {
return getLocalTasks(true);
}
@Override
public List<LocalTask> getLocalTasks(final boolean withClosed) {
synchronized (myTasks) {
return ContainerUtil.filter(myTasks.values(), new Condition<LocalTask>() {
@Override
public boolean value(final LocalTask task) {
return withClosed || !isLocallyClosed(task);
}
});
}
}
@Override
public LocalTask addTask(Task issue) {
LocalTaskImpl task = issue instanceof LocalTaskImpl ? (LocalTaskImpl)issue : new LocalTaskImpl(issue);
addTask(task);
return task;
}
@Override
public LocalTaskImpl createLocalTask(@NotNull String summary) {
return createTask(LOCAL_TASK_ID_FORMAT.format(myConfig.localTasksCounter++), summary);
}
private static LocalTaskImpl createTask(@NotNull String id, @NotNull String summary) {
LocalTaskImpl task = new LocalTaskImpl(id, summary);
Date date = new Date();
task.setCreated(date);
task.setUpdated(date);
return task;
}
@Override
public LocalTask activateTask(@NotNull final Task origin, boolean clearContext) {
LocalTask activeTask = getActiveTask();
if (origin.equals(activeTask)) return activeTask;
saveActiveTask();
if (clearContext) {
myContextManager.clearContext();
}
myContextManager.restoreContext(origin);
final LocalTask task = doActivate(origin, true);
return restoreVcsContext(task);
}
private LocalTask restoreVcsContext(LocalTask task) {
if (!isVcsEnabled()) return task;
List<ChangeListInfo> changeLists = task.getChangeLists();
if (!changeLists.isEmpty()) {
ChangeListInfo info = changeLists.get(0);
LocalChangeList changeList = myChangeListManager.getChangeList(info.id);
if (changeList == null) {
changeList = myChangeListManager.addChangeList(info.name, info.comment);
info.id = changeList.getId();
}
myChangeListManager.setDefaultChangeList(changeList);
}
List<BranchInfo> branches = task.getBranches(false);
// we should have exactly one branch per repo
MultiMap<String, BranchInfo> multiMap = new MultiMap<String, BranchInfo>();
for (BranchInfo branch : branches) {
multiMap.putValue(branch.repository, branch);
}
for (String repo: multiMap.keySet()) {
Collection<BranchInfo> infos = multiMap.get(repo);
if (infos.size() > 1) {
// cleanup needed
List<BranchInfo> existing = getAllBranches(repo);
for (Iterator<BranchInfo> iterator = infos.iterator(); iterator.hasNext(); ) {
BranchInfo info = iterator.next();
if (!existing.contains(info)) {
iterator.remove();
if (infos.size() == 1) {
break;
}
}
}
}
}
VcsTaskHandler.TaskInfo info = fromBranches(new ArrayList<BranchInfo>(multiMap.values()));
switchBranch(info);
return task;
}
private List<BranchInfo> getAllBranches(final String repo) {
ArrayList<BranchInfo> infos = new ArrayList<BranchInfo>();
VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
for (VcsTaskHandler handler : handlers) {
VcsTaskHandler.TaskInfo[] tasks = handler.getCurrentTasks();
for (VcsTaskHandler.TaskInfo info : tasks) {
infos.addAll(ContainerUtil.filter(BranchInfo.fromTaskInfo(info, false), new Condition<BranchInfo>() {
@Override
public boolean value(BranchInfo info) {
return Comparing.equal(info.repository, repo);
}
}));
}
}
return infos;
}
private void switchBranch(VcsTaskHandler.TaskInfo info) {
VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
for (VcsTaskHandler handler : handlers) {
handler.switchToTask(info, null);
}
}
private static VcsTaskHandler.TaskInfo fromBranches(List<BranchInfo> branches) {
MultiMap<String, String> map = new MultiMap<String, String>();
for (BranchInfo branch : branches) {
map.putValue(branch.name, branch.repository);
}
return new VcsTaskHandler.TaskInfo(map);
}
public void createBranch(LocalTask task, LocalTask previousActive, String name) {
VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
for (VcsTaskHandler handler : handlers) {
VcsTaskHandler.TaskInfo info = handler.getActiveTask();
if (previousActive != null && previousActive.getBranches(false).isEmpty()) {
addBranches(previousActive, info, false);
}
addBranches(task, info, true);
addBranches(task, handler.startNewTask(name), false);
}
}
public void mergeBranch(LocalTask task) {
VcsTaskHandler.TaskInfo original = fromBranches(task.getBranches(true));
VcsTaskHandler.TaskInfo feature = fromBranches(task.getBranches(false));
VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
for (VcsTaskHandler handler : handlers) {
handler.closeTask(feature, original);
}
}
private static void addBranches(LocalTask task, VcsTaskHandler.TaskInfo info, boolean original) {
List<BranchInfo> branchInfos = BranchInfo.fromTaskInfo(info, original);
for (BranchInfo branchInfo : branchInfos) {
task.addBranch(branchInfo);
}
}
private void saveActiveTask() {
myContextManager.saveContext(myActiveTask);
myActiveTask.setUpdated(new Date());
}
private LocalTask doActivate(Task origin, boolean explicitly) {
final LocalTaskImpl task = origin instanceof LocalTaskImpl ? (LocalTaskImpl)origin : new LocalTaskImpl(origin);
if (explicitly) {
task.setUpdated(new Date());
}
myActiveTask.setActive(false);
task.setActive(true);
addTask(task);
if (task.isIssue()) {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() {
public void run() {
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(myProject, "Updating " + task.getId()) {
public void run(@NotNull ProgressIndicator indicator) {
updateIssue(task.getId());
}
});
}
});
}
LocalTask oldActiveTask = myActiveTask;
boolean isChanged = !task.equals(oldActiveTask);
myActiveTask = task;
if (isChanged) {
myDispatcher.getMulticaster().taskDeactivated(oldActiveTask);
myDispatcher.getMulticaster().taskActivated(task);
}
return task;
}
private void addTask(LocalTaskImpl task) {
myTasks.put(task.getId(), task);
myDispatcher.getMulticaster().taskAdded(task);
}
@Override
public boolean testConnection(final TaskRepository repository) {
TestConnectionTask task = new TestConnectionTask("Test connection") {
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Connecting to " + repository.getUrl() + "...");
indicator.setFraction(0);
indicator.setIndeterminate(true);
try {
myConnection = repository.createCancellableConnection();
if (myConnection != null) {
Future<Exception> future = ApplicationManager.getApplication().executeOnPooledThread(myConnection);
while (true) {
try {
myException = future.get(100, TimeUnit.MILLISECONDS);
return;
}
catch (TimeoutException ignore) {
try {
indicator.checkCanceled();
}
catch (ProcessCanceledException e) {
myException = e;
myConnection.cancel();
return;
}
}
catch (Exception e) {
myException = e;
return;
}
}
}
else {
try {
repository.testConnection();
}
catch (Exception e) {
LOG.info(e);
myException = e;
}
}
}
catch (Exception e) {
myException = e;
}
}
};
ProgressManager.getInstance().run(task);
Exception e = task.myException;
if (e == null) {
myBadRepositories.remove(repository);
Messages.showMessageDialog(myProject, "Connection is successful", "Connection", Messages.getInformationIcon());
}
else if (!(e instanceof ProcessCanceledException)) {
String message = e.getMessage();
if (e instanceof UnknownHostException) {
message = "Unknown host: " + message;
}
if (message == null) {
LOG.error(e);
message = "Unknown error";
}
Messages.showErrorDialog(myProject, StringUtil.capitalize(message), "Error");
}
return e == null;
}
@NotNull
public Config getState() {
myConfig.tasks = ContainerUtil.map(myTasks.values(), new Function<Task, LocalTaskImpl>() {
public LocalTaskImpl fun(Task task) {
return new LocalTaskImpl(task);
}
});
myConfig.servers = XmlSerializer.serialize(getAllRepositories());
return myConfig;
}
public void loadState(Config config) {
XmlSerializerUtil.copyBean(config, myConfig);
myTasks.clear();
for (LocalTaskImpl task : config.tasks) {
addTask(task);
}
myRepositories.clear();
Element element = config.servers;
List<TaskRepository> repositories = loadRepositories(element);
myRepositories.addAll(repositories);
}
public static ArrayList<TaskRepository> loadRepositories(Element element) {
ArrayList<TaskRepository> repositories = new ArrayList<TaskRepository>();
for (TaskRepositoryType repositoryType : TaskRepositoryType.getRepositoryTypes()) {
for (Object o : element.getChildren()) {
if (((Element)o).getName().equals(repositoryType.getName())) {
try {
@SuppressWarnings({"unchecked"})
TaskRepository repository = (TaskRepository)XmlSerializer.deserialize((Element)o, repositoryType.getRepositoryClass());
if (repository != null) {
repository.setRepositoryType(repositoryType);
repositories.add(repository);
}
}
catch (XmlSerializationException e) {
// ignore
LOG.error(e.getMessage());
}
}
}
}
return repositories;
}
public void projectOpened() {
TaskProjectConfiguration projectConfiguration = getProjectConfiguration();
servers:
for (TaskProjectConfiguration.SharedServer server : projectConfiguration.servers) {
if (server.type == null || server.url == null) {
continue;
}
for (TaskRepositoryType<?> repositoryType : TaskRepositoryType.getRepositoryTypes()) {
if (repositoryType.getName().equals(server.type)) {
for (TaskRepository repository : myRepositories) {
if (!repositoryType.equals(repository.getRepositoryType())) {
continue;
}
if (server.url.equals(repository.getUrl())) {
continue servers;
}
}
TaskRepository repository = repositoryType.createRepository();
repository.setUrl(server.url);
myRepositories.add(repository);
}
}
}
myContextManager.pack(200, 50);
// make sure the task is associated with default changelist
LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
LocalChangeList defaultList = myChangeListManager.findChangeList(LocalChangeList.DEFAULT_NAME);
if (defaultList != null && defaultTask != null) {
ChangeListInfo listInfo = new ChangeListInfo(defaultList);
if (!defaultTask.getChangeLists().contains(listInfo)) {
defaultTask.addChangelist(listInfo);
}
}
// remove already not existing changelists from tasks changelists
for (LocalTask localTask : getLocalTasks()) {
for (Iterator<ChangeListInfo> iterator = localTask.getChangeLists().iterator(); iterator.hasNext(); ) {
final ChangeListInfo changeListInfo = iterator.next();
if (myChangeListManager.getChangeList(changeListInfo.id) == null) {
iterator.remove();
}
}
}
myChangeListManager.addChangeListListener(myChangeListListener);
}
private TaskProjectConfiguration getProjectConfiguration() {
return ServiceManager.getService(myProject, TaskProjectConfiguration.class);
}
public void projectClosed() {
}
@NotNull
public String getComponentName() {
return "Task Manager";
}
public void initComponent() {
if (!ApplicationManager.getApplication().isUnitTestMode()) {
myCacheRefreshTimer = UIUtil.createNamedTimer("TaskManager refresh", myConfig.updateInterval * 60 * 1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (myConfig.updateEnabled && !myUpdating) {
updateIssues(null);
}
}
});
myCacheRefreshTimer.setInitialDelay(0);
StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
public void run() {
myCacheRefreshTimer.start();
}
});
}
// make sure that the default task is exist
LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
if (defaultTask == null) {
defaultTask = createDefaultTask();
addTask(defaultTask);
}
// search for active task
LocalTask activeTask = null;
final List<LocalTask> tasks = getLocalTasks();
Collections.sort(tasks, TASK_UPDATE_COMPARATOR);
for (LocalTask task : tasks) {
if (activeTask == null) {
if (task.isActive()) {
activeTask = task;
}
}
else {
task.setActive(false);
}
}
if (activeTask == null) {
activeTask = defaultTask;
}
myActiveTask = activeTask;
doActivate(myActiveTask, false);
myDispatcher.getMulticaster().taskActivated(myActiveTask);
}
private static LocalTaskImpl createDefaultTask() {
return new LocalTaskImpl(LocalTaskImpl.DEFAULT_TASK_ID, "Default task");
}
public void disposeComponent() {
if (myCacheRefreshTimer != null) {
myCacheRefreshTimer.stop();
}
myChangeListManager.removeChangeListListener(myChangeListListener);
}
public void updateIssues(final @Nullable Runnable onComplete) {
TaskRepository first = ContainerUtil.find(getAllRepositories(), new Condition<TaskRepository>() {
public boolean value(TaskRepository repository) {
return repository.isConfigured();
}
});
if (first == null) {
myIssueCache.clear();
if (onComplete != null) {
onComplete.run();
}
return;
}
myUpdating = true;
if (ApplicationManager.getApplication().isUnitTestMode()) {
doUpdate(onComplete);
}
else {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
public void run() {
doUpdate(onComplete);
}
});
}
}
private void doUpdate(@Nullable Runnable onComplete) {
try {
List<Task> issues = getIssuesFromRepositories(null, 0, myConfig.updateIssuesCount, false, false, new EmptyProgressIndicator());
if (issues == null) return;
synchronized (myIssueCache) {
myIssueCache.clear();
for (Task issue : issues) {
myIssueCache.put(issue.getId(), issue);
}
}
// update local tasks
synchronized (myTasks) {
for (Map.Entry<String, LocalTask> entry : myTasks.entrySet()) {
Task issue = myIssueCache.get(entry.getKey());
if (issue != null) {
entry.getValue().updateFromIssue(issue);
}
}
}
}
finally {
if (onComplete != null) {
onComplete.run();
}
myUpdating = false;
}
}
@Nullable
private List<Task> getIssuesFromRepositories(@Nullable String request,
int offset,
int limit,
boolean withClosed,
boolean forceRequest,
@NotNull final ProgressIndicator cancelled) {
List<Task> issues = null;
for (final TaskRepository repository : getAllRepositories()) {
if (!repository.isConfigured() || (!forceRequest && myBadRepositories.contains(repository))) {
continue;
}
try {
long start = System.currentTimeMillis();
Task[] tasks = repository.getIssues(request, offset, limit, withClosed, cancelled);
long timeSpent = System.currentTimeMillis() - start;
LOG.debug(String.format("Total %s ms to download %d issues from '%s'", timeSpent, tasks.length, repository.getUrl()));
myBadRepositories.remove(repository);
if (issues == null) issues = new ArrayList<Task>(tasks.length);
if (!repository.isSupported(TaskRepository.NATIVE_SEARCH) && request != null) {
List<Task> filteredTasks = TaskSearchSupport.filterTasks(request, ContainerUtil.list(tasks));
ContainerUtil.addAll(issues, filteredTasks);
}
else {
ContainerUtil.addAll(issues, tasks);
}
}
catch (ProcessCanceledException ignored) {
// OK
}
catch (Exception e) {
String reason = "";
// Fix to IDEA-111810
//noinspection InstanceofCatchParameter
if (e.getClass() == Exception.class || e instanceof RequestFailedException) {
// probably contains some message meaningful to end-user
reason = e.getMessage();
}
//noinspection InstanceofCatchParameter
if (e instanceof SocketTimeoutException) {
LOG.warn("Socket timeout from " + repository);
}
else {
LOG.warn("Cannot connect to " + repository, e);
}
myBadRepositories.add(repository);
if (forceRequest) {
notifyAboutConnectionFailure(repository, reason);
}
}
}
return issues;
}
private void notifyAboutConnectionFailure(final TaskRepository repository, String details) {
Notifications.Bus.register(TASKS_NOTIFICATION_GROUP, NotificationDisplayType.BALLOON);
String content = "<p><a href=\"\">Configure server...</a></p>";
if (!StringUtil.isEmpty(details)) {
content = "<p>" + details + "</p>" + content;
}
Notifications.Bus.notify(new Notification(TASKS_NOTIFICATION_GROUP, "Cannot connect to " + repository.getUrl(),
content, NotificationType.WARNING,
new NotificationListener() {
public void hyperlinkUpdate(@NotNull Notification notification,
@NotNull HyperlinkEvent event) {
TaskRepositoriesConfigurable configurable =
new TaskRepositoriesConfigurable(myProject);
ShowSettingsUtil.getInstance().editConfigurable(myProject, configurable);
if (!ArrayUtil.contains(repository, getAllRepositories())) {
notification.expire();
}
}
}), myProject);
}
@Override
public boolean isVcsEnabled() {
return ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss().length > 0;
}
@Override
public AbstractVcs getActiveVcs() {
AbstractVcs[] vcss = ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss();
if (vcss.length == 0) return null;
for (AbstractVcs vcs : vcss) {
if (vcs.getType() == VcsType.distributed) {
return vcs;
}
}
return vcss[0];
}
@Override
public boolean isLocallyClosed(@NotNull LocalTask localTask) {
if (isVcsEnabled()) {
List<ChangeListInfo> lists = localTask.getChangeLists();
if (lists.isEmpty()) return true;
for (ChangeListInfo list : lists) {
if (StringUtil.isEmpty(list.id)) {
return true;
}
}
}
return false;
}
@Nullable
@Override
public LocalTask getAssociatedTask(@NotNull LocalChangeList list) {
for (LocalTask task : getLocalTasks()) {
for (ChangeListInfo changeListInfo : task.getChangeLists()) {
if (changeListInfo.id.equals(list.getId())) {
return task;
}
}
}
return null;
}
@Override
public void trackContext(@NotNull LocalChangeList changeList) {
ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
String changeListName = changeList.getName();
LocalTaskImpl task = createLocalTask(changeListName);
task.addChangelist(changeListInfo);
addTask(task);
if (changeList.isDefault()) {
activateTask(task, false);
}
}
@Override
public void disassociateFromTask(@NotNull LocalChangeList changeList) {
ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
for (LocalTask localTask : getLocalTasks()) {
if (localTask.getChangeLists().contains(changeListInfo)) {
localTask.removeChangelist(changeListInfo);
}
}
}
public void decorateChangeList(@NotNull LocalChangeList changeList,
@NotNull ColoredTreeCellRenderer cellRenderer,
boolean selected,
boolean expanded,
boolean hasFocus) {
LocalTask task = getAssociatedTask(changeList);
if (task != null && task.isIssue()) {
cellRenderer.setIcon(task.getIcon());
}
}
public void createChangeList(@NotNull LocalTask task, String name) {
String comment = TaskUtil.getChangeListComment(task);
createChangeList(task, name, comment);
}
private void createChangeList(LocalTask task, String name, @Nullable String comment) {
LocalChangeList changeList = myChangeListManager.findChangeList(name);
if (changeList == null) {
changeList = myChangeListManager.addChangeList(name, comment);
}
else {
final LocalTask associatedTask = getAssociatedTask(changeList);
if (associatedTask != null) {
associatedTask.removeChangelist(new ChangeListInfo(changeList));
}
changeList.setComment(comment);
}
task.addChangelist(new ChangeListInfo(changeList));
myChangeListManager.setDefaultChangeList(changeList);
}
public String getChangelistName(Task task) {
String name = task.isIssue() && myConfig.changelistNameFormat != null
? TaskUtil.formatTask(task, myConfig.changelistNameFormat)
: task.getSummary();
return StringUtil.shortenTextWithEllipsis(name, 100, 0);
}
public String suggestBranchName(Task task) {
if (task.isIssue() && StringUtil.isNotEmpty(task.getNumber())) {
return task.getId().replace(' ', '-');
}
else {
String summary = task.getSummary();
List<String> words = StringUtil.getWordsIn(summary);
String[] strings = ArrayUtil.toStringArray(words);
return StringUtil.join(strings, 0, Math.min(2, strings.length), "-");
}
}
@TestOnly
public ChangeListAdapter getChangeListListener() {
return myChangeListListener;
}
/**
* Reconfigure repository's HTTP clients probably to apply new connection settings.
*/
public void reconfigureRepositoryClients() {
for (TaskRepository repository : myRepositories) {
if (repository instanceof BaseRepositoryImpl) {
((BaseRepositoryImpl)repository).reconfigureClient();
}
}
}
public static class Config {
@Property(surroundWithTag = false)
@AbstractCollection(surroundWithTag = false, elementTag = "task")
public List<LocalTaskImpl> tasks = new ArrayList<LocalTaskImpl>();
public int localTasksCounter = 1;
public int taskHistoryLength = 50;
public boolean updateEnabled = true;
public int updateInterval = 20;
public int updateIssuesCount = 100;
// create task options
public boolean clearContext = true;
public boolean createChangelist = true;
public boolean createBranch = true;
// close task options
public boolean closeIssue = true;
public boolean commitChanges = true;
public boolean mergeBranch = true;
public boolean saveContextOnCommit = true;
public boolean trackContextForNewChangelist = false;
public boolean markAsInProgress = false;
public String changelistNameFormat = "{id} {summary}";
public boolean searchClosedTasks = false;
@Tag("servers")
public Element servers = new Element("servers");
}
private abstract class TestConnectionTask extends com.intellij.openapi.progress.Task.Modal {
protected Exception myException;
@Nullable
protected TaskRepository.CancellableConnection myConnection;
public TestConnectionTask(String title) {
super(TaskManagerImpl.this.myProject, title, true);
}
@Override
public void onCancel() {
if (myConnection != null) {
myConnection.cancel();
}
}
}
}