| /******************************************************************************* |
| * Copyright (c) 2004, 2014 Tasktop Technologies and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * https://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Tasktop Technologies - initial API and implementation |
| * Peter Stibrany - improvements for bug 271197 |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.tasks.ui.util; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.core.commands.ExecutionEvent; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.mylyn.commons.core.CoreUtil; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.commons.net.Policy; |
| import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; |
| import org.eclipse.mylyn.internal.tasks.core.TaskAttachment; |
| import org.eclipse.mylyn.internal.tasks.core.data.FileTaskAttachmentSource; |
| import org.eclipse.mylyn.internal.tasks.core.sync.SubmitTaskAttachmentJob; |
| import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; |
| import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; |
| import org.eclipse.mylyn.tasks.core.ITask; |
| import org.eclipse.mylyn.tasks.core.ITaskAttachment; |
| import org.eclipse.mylyn.tasks.core.RepositoryStatus; |
| import org.eclipse.mylyn.tasks.core.TaskRepository; |
| import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttribute; |
| import org.eclipse.mylyn.tasks.core.data.TaskData; |
| import org.eclipse.mylyn.tasks.core.sync.SubmitJob; |
| import org.eclipse.mylyn.tasks.ui.TasksUi; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.handlers.HandlerUtil; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| /** |
| * @author Steffen Pingel |
| */ |
| public class AttachmentUtil { |
| |
| protected static final int BUFFER_SIZE = 1024; |
| |
| public static final String CONTEXT_DESCRIPTION = "mylyn/context/zip"; //$NON-NLS-1$ |
| |
| private static final String CONTEXT_DESCRIPTION_LEGACY = "mylar/context/zip"; //$NON-NLS-1$ |
| |
| private static final String CONTEXT_FILENAME = "mylyn-context.zip"; //$NON-NLS-1$ |
| |
| private static final String CONTEXT_CONTENT_TYPE = "application/octet-stream"; //$NON-NLS-1$ |
| |
| private static final String ATTACHMENT_DEFAULT_NAME = "attachment"; //$NON-NLS-1$ |
| |
| private static final String CTYPE_ZIP = "zip"; //$NON-NLS-1$ |
| |
| private static final String CTYPE_TEXT = "text"; //$NON-NLS-1$ |
| |
| private static final String CTYPE_HTML = "html"; //$NON-NLS-1$ |
| |
| public static boolean postContext(AbstractRepositoryConnector connector, TaskRepository repository, ITask task, |
| String comment, TaskAttribute attribute, IProgressMonitor monitor) throws CoreException { |
| AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); |
| TasksUiPlugin.getContextStore().saveActiveContext(); |
| |
| File file = TasksUiPlugin.getContextStore().getFileForContext(task); |
| if (attachmentHandler != null && file != null && file.exists()) { |
| FileTaskAttachmentSource attachment = new FileTaskAttachmentSource(file); |
| attachment.setDescription(CONTEXT_DESCRIPTION); |
| attachment.setName(CONTEXT_FILENAME); |
| attachmentHandler.postContent(repository, task, attachment, comment, attribute, monitor); |
| return true; |
| } |
| return false; |
| } |
| |
| public static List<ITaskAttachment> getContextAttachments(TaskRepository repository, ITask task) { |
| List<ITaskAttachment> contextAttachments = new ArrayList<ITaskAttachment>(); |
| TaskData taskData; |
| try { |
| taskData = TasksUi.getTaskDataManager().getTaskData(task); |
| } catch (CoreException e) { |
| // ignore |
| return contextAttachments; |
| } |
| if (taskData != null) { |
| List<TaskAttribute> taskAttachments = taskData.getAttributeMapper().getAttributesByType(taskData, |
| TaskAttribute.TYPE_ATTACHMENT); |
| for (TaskAttribute attribute : taskAttachments) { |
| TaskAttachment taskAttachment = new TaskAttachment(repository, task, attribute); |
| taskData.getAttributeMapper().updateTaskAttachment(taskAttachment, attribute); |
| if (isContext(taskAttachment)) { |
| contextAttachments.add(taskAttachment); |
| } |
| } |
| } |
| return contextAttachments; |
| } |
| |
| public static boolean hasContextAttachment(ITask task) { |
| Assert.isNotNull(task); |
| TaskRepository repository = TasksUi.getRepositoryManager().getRepository(task.getConnectorKind(), |
| task.getRepositoryUrl()); |
| List<ITaskAttachment> contextAttachments = getContextAttachments(repository, task); |
| return contextAttachments.size() > 0; |
| } |
| |
| public static boolean downloadContext(final ITask task, final ITaskAttachment attachment, |
| final IRunnableContext context) { |
| if (task.isActive()) { |
| TasksUi.getTaskActivityManager().deactivateTask(task); |
| } |
| try { |
| context.run(true, true, new IRunnableWithProgress() { |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
| File targetFile = TasksUiPlugin.getContextStore().getFileForContext(task); |
| try { |
| boolean exceptionThrown = true; |
| OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile)); |
| try { |
| AttachmentUtil.downloadAttachment(attachment, out, monitor); |
| exceptionThrown = false; |
| } catch (CoreException e) { |
| throw new InvocationTargetException(e); |
| } finally { |
| out.close(); |
| if (exceptionThrown) { |
| targetFile.delete(); |
| } |
| } |
| } catch (OperationCanceledException e) { |
| throw new InterruptedException(); |
| } catch (IOException e) { |
| throw new InvocationTargetException( |
| new CoreException(new RepositoryStatus(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| RepositoryStatus.ERROR_IO, "Error writing to context file", e))); //$NON-NLS-1$ |
| } |
| } |
| }); |
| } catch (InvocationTargetException e) { |
| if (e.getCause() instanceof CoreException) { |
| TasksUiInternal.displayStatus(Messages.AttachmentUtil_Mylyn_Information, |
| ((CoreException) e.getCause()).getStatus()); |
| } else { |
| StatusManager.getManager() |
| .handle(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| "Unexpected error while retrieving context", e), StatusManager.SHOW | StatusManager.LOG); //$NON-NLS-1$ |
| } |
| return false; |
| } catch (InterruptedException ignored) { |
| // canceled |
| return false; |
| } |
| TasksUiInternal.getTaskList().notifyElementChanged(task); |
| TasksUiInternal.activateTaskThroughCommand(task); |
| return true; |
| } |
| |
| public static boolean uploadContext(final TaskRepository repository, final ITask task, final String comment, |
| final IRunnableContext context) { |
| TasksUiPlugin.getContextStore().saveActiveContext(); |
| File sourceContextFile = TasksUiPlugin.getContextStore().getFileForContext(task); |
| if (!sourceContextFile.exists()) { |
| TasksUiInternal.displayStatus(Messages.AttachmentUtil_Mylyn_Information, new Status(IStatus.WARNING, |
| TasksUiPlugin.ID_PLUGIN, Messages.AttachmentUtil_The_context_is_empty)); |
| return false; |
| } |
| |
| FileTaskAttachmentSource source = new FileTaskAttachmentSource(sourceContextFile); |
| source.setName(CONTEXT_FILENAME); |
| source.setDescription(CONTEXT_DESCRIPTION); |
| source.setContentType(CONTEXT_CONTENT_TYPE); |
| AbstractRepositoryConnector connector = TasksUi.getRepositoryManager().getRepositoryConnector( |
| repository.getConnectorKind()); |
| TaskAttribute taskAttribute = null; |
| try { |
| TaskData taskData = TasksUi.getTaskDataManager().getTaskData(task); |
| if (taskData != null) { |
| taskAttribute = taskData.getRoot().createMappedAttribute(TaskAttribute.NEW_ATTACHMENT); |
| } |
| } catch (CoreException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, |
| "Unexpected error while attaching context. Continuing with task submission.", e)); //$NON-NLS-1$ |
| } |
| final SubmitJob submitJob = TasksUiInternal.getJobFactory().createSubmitTaskAttachmentJob(connector, |
| repository, task, source, comment, taskAttribute); |
| try { |
| context.run(true, true, new IRunnableWithProgress() { |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
| if (((SubmitTaskAttachmentJob) submitJob).run(monitor) == Status.CANCEL_STATUS) { |
| throw new InterruptedException(); |
| } |
| } |
| }); |
| } catch (InvocationTargetException e) { |
| StatusManager.getManager() |
| .handle(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| "Unexpected error while attaching context", e), StatusManager.SHOW | StatusManager.LOG); //$NON-NLS-1$ |
| return false; |
| } catch (InterruptedException ignored) { |
| // canceled |
| return false; |
| } |
| IStatus status = submitJob.getStatus(); |
| if (status != null && status.getSeverity() != IStatus.CANCEL) { |
| TasksUiInternal.displayStatus(Messages.AttachmentUtil_Mylyn_Information, submitJob.getStatus()); |
| return false; |
| } |
| return true; |
| } |
| |
| public static boolean hasLocalContext(ITask task) { |
| Assert.isNotNull(task); |
| return TasksUiPlugin.getContextStore().hasContext(task); |
| } |
| |
| public static boolean isContext(ITaskAttachment attachment) { |
| return CONTEXT_DESCRIPTION.equals(attachment.getDescription()) |
| || CONTEXT_DESCRIPTION_LEGACY.equals(attachment.getDescription()); |
| } |
| |
| public static boolean canUploadAttachment(ITask task) { |
| TaskRepository repository = TasksUi.getRepositoryManager().getRepository(task.getConnectorKind(), |
| task.getRepositoryUrl()); |
| if (repository != null) { |
| AbstractRepositoryConnector connector = TasksUi.getRepositoryManager().getRepositoryConnector( |
| repository.getConnectorKind()); |
| AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); |
| if (attachmentHandler != null) { |
| return attachmentHandler.canPostContent(repository, task); |
| } |
| } |
| return false; |
| } |
| |
| public static boolean canDownloadAttachment(ITask task) { |
| TaskRepository repository = TasksUi.getRepositoryManager().getRepository(task.getConnectorKind(), |
| task.getRepositoryUrl()); |
| if (repository != null) { |
| AbstractRepositoryConnector connector = TasksUi.getRepositoryManager().getRepositoryConnector( |
| repository.getConnectorKind()); |
| AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); |
| if (attachmentHandler != null) { |
| return attachmentHandler.canGetContent(repository, task); |
| } |
| } |
| return false; |
| } |
| |
| public static void downloadAttachment(ITaskAttachment attachment, OutputStream out, IProgressMonitor monitor) |
| throws CoreException { |
| try { |
| monitor.beginTask(Messages.AttachmentUtil_Downloading_attachment, IProgressMonitor.UNKNOWN); |
| |
| AbstractRepositoryConnector connector = TasksUi.getRepositoryManager().getRepositoryConnector( |
| attachment.getConnectorKind()); |
| AbstractTaskAttachmentHandler handler = connector.getTaskAttachmentHandler(); |
| if (handler == null) { |
| throw new CoreException(new RepositoryStatus(IStatus.INFO, TasksUiPlugin.ID_PLUGIN, |
| RepositoryStatus.ERROR_INTERNAL, "The repository does not support attachments.")); //$NON-NLS-1$ |
| } |
| |
| InputStream in = handler.getContent(attachment.getTaskRepository(), attachment.getTask(), |
| attachment.getTaskAttribute(), monitor); |
| try { |
| byte[] buffer = new byte[BUFFER_SIZE]; |
| while (true) { |
| Policy.checkCanceled(monitor); |
| int count = in.read(buffer); |
| if (count == -1) { |
| return; |
| } |
| out.write(buffer, 0, count); |
| } |
| } catch (IOException e) { |
| throw new CoreException(new RepositoryStatus(attachment.getTaskRepository(), IStatus.ERROR, |
| TasksUiPlugin.ID_PLUGIN, RepositoryStatus.ERROR_IO, "IO error reading attachment: " //$NON-NLS-1$ |
| + e.getMessage(), e)); |
| } finally { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, |
| "Error closing attachment stream", e)); //$NON-NLS-1$ |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| public static ITaskAttachment getSelectedAttachment() { |
| List<ITaskAttachment> attachments = getSelectedAttachments(null); |
| if (attachments.isEmpty()) { |
| return null; |
| } |
| return attachments.get(0); |
| } |
| |
| public static List<ITaskAttachment> getSelectedAttachments(ExecutionEvent event) { |
| if (event == null) { |
| // create bogus execution event to obtain active menu selection |
| IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); |
| if (service != null) { |
| event = new ExecutionEvent(null, Collections.emptyMap(), null, service.getCurrentState()); |
| } else { |
| // failed |
| return Collections.emptyList(); |
| } |
| } |
| |
| // prefer local selection, e.g from table in Task Editor |
| ISelection selection = HandlerUtil.getActiveMenuSelection(event); |
| if (selection == null || selection.isEmpty()) { |
| selection = HandlerUtil.getCurrentSelection(event); |
| } |
| if (selection instanceof IStructuredSelection && !selection.isEmpty()) { |
| List<?> items = ((IStructuredSelection) selection).toList(); |
| List<ITaskAttachment> attachments = new ArrayList<ITaskAttachment>(); |
| for (Object item : items) { |
| if (item instanceof ITaskAttachment) { |
| attachments.add((ITaskAttachment) item); |
| } |
| } |
| return attachments; |
| } |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * @deprecated use {@link #getSelectedAttachments(ExecutionEvent)} instead |
| */ |
| @Deprecated |
| public static List<ITaskAttachment> getSelectedAttachments() { |
| IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| if (window != null) { |
| ISelection windowSelection = window.getSelectionService().getSelection(); |
| if (windowSelection instanceof IStructuredSelection && !windowSelection.isEmpty()) { |
| IStructuredSelection selection = (IStructuredSelection) windowSelection; |
| List<?> items = selection.toList(); |
| |
| List<ITaskAttachment> attachments = new ArrayList<ITaskAttachment>(); |
| for (Object item : items) { |
| if (item instanceof ITaskAttachment) { |
| attachments.add((ITaskAttachment) item); |
| } |
| } |
| return attachments; |
| } |
| } |
| return Collections.emptyList(); |
| } |
| |
| public static String getAttachmentFilename(ITaskAttachment attachment) { |
| Assert.isNotNull(attachment); |
| String name = attachment.getFileName(); |
| if (name != null && name.length() > 0) { |
| return CoreUtil.asFileName(name); |
| } else { |
| String extension = ""; //$NON-NLS-1$ |
| |
| // if no filename is set, make one up with the proper extension so |
| // we can support opening in that filetype's default editor |
| String ctype = attachment.getContentType(); |
| if (ctype != null) { |
| if (ctype.endsWith(CTYPE_HTML)) { |
| extension = ".html"; //$NON-NLS-1$ |
| } else if (ctype.startsWith(CTYPE_TEXT)) { |
| extension = ".txt"; //$NON-NLS-1$ |
| } else if (ctype.endsWith(CTYPE_ZIP)) { |
| extension = ".zip"; //$NON-NLS-1$ |
| } else { |
| int i = ctype.lastIndexOf("/"); //$NON-NLS-1$ |
| if (i != -1) { |
| extension = "." + ctype.substring(i + 1); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| return ATTACHMENT_DEFAULT_NAME + extension; |
| } |
| } |
| |
| } |