| /* |
| * Copyright 2000-2014 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.context; |
| |
| import com.intellij.notification.Notification; |
| import com.intellij.notification.NotificationType; |
| import com.intellij.notification.Notifications; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.InvalidDataException; |
| import com.intellij.openapi.util.JDOMUtil; |
| import com.intellij.openapi.util.WriteExternalException; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.tasks.Task; |
| import com.intellij.util.NullableFunction; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.io.zip.JBZipEntry; |
| import com.intellij.util.io.zip.JBZipFile; |
| import org.jdom.Document; |
| import org.jdom.Element; |
| import org.jdom.output.XMLOutputter; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| /** |
| * @author Dmitry Avdeev |
| */ |
| public class WorkingContextManager { |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.context.WorkingContextManager"); |
| @NonNls private static final String TASKS_FOLDER = "tasks"; |
| |
| private final Project myProject; |
| @NonNls private static final String TASKS_ZIP_POSTFIX = ".tasks.zip"; |
| @NonNls private static final String TASK_XML_POSTFIX = ".task.xml"; |
| private static final String CONTEXT_ZIP_POSTFIX = ".contexts.zip"; |
| private static final Comparator<JBZipEntry> ENTRY_COMPARATOR = new Comparator<JBZipEntry>() { |
| public int compare(JBZipEntry o1, JBZipEntry o2) { |
| return Long.signum(o2.getTime() - o1.getTime()); |
| } |
| }; |
| |
| public static WorkingContextManager getInstance(Project project) { |
| return ServiceManager.getService(project, WorkingContextManager.class); |
| } |
| |
| public WorkingContextManager(Project project) { |
| myProject = project; |
| } |
| |
| public void loadContext(Element fromElement) { |
| for (WorkingContextProvider provider : Extensions.getExtensions(WorkingContextProvider.EP_NAME, myProject)) { |
| try { |
| Element child = fromElement.getChild(provider.getId()); |
| if (child != null) { |
| provider.loadContext(child); |
| } |
| } |
| catch (InvalidDataException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| |
| public void saveContext(Element toElement) { |
| for (WorkingContextProvider provider : Extensions.getExtensions(WorkingContextProvider.EP_NAME, myProject)) { |
| try { |
| Element child = new Element(provider.getId()); |
| provider.saveContext(child); |
| toElement.addContent(child); |
| } |
| catch (WriteExternalException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| |
| public void clearContext() { |
| for (WorkingContextProvider provider : Extensions.getExtensions(WorkingContextProvider.EP_NAME, myProject)) { |
| provider.clearContext(); |
| } |
| } |
| |
| public void saveContext(Task task) { |
| String entryName = task.getId() + TASK_XML_POSTFIX; |
| saveContext(entryName, TASKS_ZIP_POSTFIX, task.getSummary()); |
| } |
| |
| public void saveContext(@Nullable String entryName, @Nullable String comment) { |
| saveContext(entryName, CONTEXT_ZIP_POSTFIX, comment); |
| } |
| |
| private synchronized void saveContext(@Nullable String entryName, String zipPostfix, @Nullable String comment) { |
| JBZipFile archive = null; |
| try { |
| archive = getTasksArchive(zipPostfix); |
| if (entryName == null) { |
| int i = archive.getEntries().size(); |
| do { |
| entryName = "context" + i++; |
| } while (archive.getEntry("/" + entryName) != null); |
| } |
| JBZipEntry entry = archive.getOrCreateEntry("/" + entryName); |
| if (comment != null) { |
| entry.setComment(comment); |
| } |
| Element element = new Element("context"); |
| saveContext(element); |
| String s = new XMLOutputter().outputString(element); |
| entry.setData(s.getBytes(CharsetToolkit.UTF8_CHARSET)); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| finally { |
| closeArchive(archive); |
| } |
| } |
| |
| private JBZipFile getTasksArchive(String postfix) throws IOException { |
| File file = getArchiveFile(postfix); |
| try { |
| return new JBZipFile(file); |
| } |
| catch (IOException e) { |
| file.delete(); |
| JBZipFile zipFile = null; |
| try { |
| zipFile = new JBZipFile(file); |
| Notifications.Bus.notify(new Notification("Tasks", "Context Data Corrupted", |
| "Context information history for " + myProject.getName() + " was corrupted.\n" + |
| "The history was replaced with empty one.", NotificationType.ERROR), myProject); |
| } |
| catch (IOException e1) { |
| LOG.error("Can't repair form context data corruption", e1); |
| } |
| return zipFile; |
| } |
| } |
| |
| private File getArchiveFile(String postfix) { |
| File tasksFolder = new File(PathManager.getConfigPath(), TASKS_FOLDER); |
| if (!tasksFolder.exists()) { |
| //noinspection ResultOfMethodCallIgnored |
| tasksFolder.mkdirs(); |
| } |
| String projectName = FileUtil.sanitizeFileName(myProject.getName()); |
| return new File(tasksFolder, projectName + postfix); |
| } |
| |
| public void restoreContext(@NotNull Task task) { |
| loadContext(TASKS_ZIP_POSTFIX, task.getId() + TASK_XML_POSTFIX); |
| } |
| |
| private synchronized boolean loadContext(String zipPostfix, String entryName) { |
| JBZipFile archive = null; |
| try { |
| archive = getTasksArchive(zipPostfix); |
| JBZipEntry entry = archive.getEntry(StringUtil.startsWithChar(entryName, '/') ? entryName : "/" + entryName); |
| if (entry != null) { |
| byte[] bytes = entry.getData(); |
| Document document = JDOMUtil.loadDocument(new String(bytes)); |
| Element rootElement = document.getRootElement(); |
| loadContext(rootElement); |
| return true; |
| } |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| } |
| finally { |
| closeArchive(archive); |
| } |
| return false; |
| } |
| |
| private static void closeArchive(JBZipFile archive) { |
| if (archive != null) { |
| try { |
| archive.close(); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| |
| public List<ContextInfo> getContextHistory() { |
| return getContextHistory(CONTEXT_ZIP_POSTFIX); |
| } |
| |
| private synchronized List<ContextInfo> getContextHistory(String zipPostfix) { |
| JBZipFile archive = null; |
| try { |
| archive = getTasksArchive(zipPostfix); |
| List<JBZipEntry> entries = archive.getEntries(); |
| return ContainerUtil.mapNotNull(entries, new NullableFunction<JBZipEntry, ContextInfo>() { |
| public ContextInfo fun(JBZipEntry entry) { |
| return entry.getName().startsWith("/context") ? new ContextInfo(entry.getName(), entry.getTime(), entry.getComment()) : null; |
| } |
| }); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| return Collections.emptyList(); |
| } |
| finally { |
| closeArchive(archive); |
| } |
| } |
| |
| public boolean loadContext(String name) { |
| return loadContext(CONTEXT_ZIP_POSTFIX, name); |
| } |
| |
| public void removeContext(String name) { |
| removeContext(name, CONTEXT_ZIP_POSTFIX); |
| } |
| |
| public void removeContext(Task task) { |
| removeContext(task.getId(), TASKS_ZIP_POSTFIX); |
| } |
| |
| private void removeContext(String name, String postfix) { |
| JBZipFile archive = null; |
| try { |
| archive = getTasksArchive(postfix); |
| JBZipEntry entry = archive.getEntry(name); |
| if (entry != null) { |
| archive.eraseEntry(entry); |
| } |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| finally { |
| closeArchive(archive); |
| } |
| } |
| |
| public void pack(int max, int delta) { |
| pack(max, delta, CONTEXT_ZIP_POSTFIX); |
| pack(max, delta, TASKS_ZIP_POSTFIX); |
| } |
| |
| private synchronized void pack(int max, int delta, String zipPostfix) { |
| JBZipFile archive = null; |
| try { |
| archive = getTasksArchive(zipPostfix); |
| List<JBZipEntry> entries = archive.getEntries(); |
| if (entries.size() > max + delta) { |
| JBZipEntry[] array = entries.toArray(new JBZipEntry[entries.size()]); |
| Arrays.sort(array, ENTRY_COMPARATOR); |
| for (int i = array.length - 1; i >= max; i--) { |
| archive.eraseEntry(array[i]); |
| } |
| } |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| finally { |
| closeArchive(archive); |
| } |
| } |
| |
| @TestOnly |
| public File getContextFile() throws IOException { |
| return getArchiveFile(CONTEXT_ZIP_POSTFIX); |
| } |
| } |