PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/platform/tasks-platform-impl/src/com/intellij/tasks/context/WorkingContextManager.java

http://github.com/JetBrains/intellij-community
Java | 267 lines | 235 code | 30 blank | 2 comment | 23 complexity | 23b95ee8f5b11bcca28ceeea7172b62f MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
  2. package com.intellij.tasks.context;
  3. import com.intellij.notification.Notification;
  4. import com.intellij.notification.NotificationType;
  5. import com.intellij.notification.Notifications;
  6. import com.intellij.openapi.Disposable;
  7. import com.intellij.openapi.application.ApplicationManager;
  8. import com.intellij.openapi.application.PathManager;
  9. import com.intellij.openapi.components.Service;
  10. import com.intellij.openapi.diagnostic.Logger;
  11. import com.intellij.openapi.project.Project;
  12. import com.intellij.openapi.util.Disposer;
  13. import com.intellij.openapi.util.InvalidDataException;
  14. import com.intellij.openapi.util.JDOMUtil;
  15. import com.intellij.openapi.util.WriteExternalException;
  16. import com.intellij.openapi.util.io.FileUtil;
  17. import com.intellij.openapi.util.text.StringUtil;
  18. import com.intellij.tasks.Task;
  19. import com.intellij.util.NullableFunction;
  20. import com.intellij.util.ThrowableConsumer;
  21. import com.intellij.util.containers.ContainerUtil;
  22. import com.intellij.util.io.zip.JBZipEntry;
  23. import com.intellij.util.io.zip.JBZipFile;
  24. import org.jdom.Element;
  25. import org.jdom.output.XMLOutputter;
  26. import org.jetbrains.annotations.NonNls;
  27. import org.jetbrains.annotations.NotNull;
  28. import org.jetbrains.annotations.Nullable;
  29. import org.jetbrains.annotations.TestOnly;
  30. import java.io.File;
  31. import java.io.IOException;
  32. import java.nio.charset.StandardCharsets;
  33. import java.util.Arrays;
  34. import java.util.Collections;
  35. import java.util.Comparator;
  36. import java.util.List;
  37. @Service
  38. public final class WorkingContextManager {
  39. private static final Logger LOG = Logger.getInstance(WorkingContextManager.class);
  40. @NonNls private static final String TASKS_FOLDER = "tasks";
  41. private final Project myProject;
  42. @NonNls private static final String TASKS_ZIP_POSTFIX = ".tasks.zip";
  43. @NonNls private static final String TASK_XML_POSTFIX = ".task.xml";
  44. private static final String CONTEXT_ZIP_POSTFIX = ".contexts.zip";
  45. private static final Comparator<JBZipEntry> ENTRY_COMPARATOR = (o1, o2) -> Long.signum(o2.getTime() - o1.getTime());
  46. private boolean ENABLED;
  47. public static WorkingContextManager getInstance(@NotNull Project project) {
  48. return project.getService(WorkingContextManager.class);
  49. }
  50. public WorkingContextManager(@NotNull Project project) {
  51. myProject = project;
  52. ENABLED = !ApplicationManager.getApplication().isUnitTestMode();
  53. }
  54. @TestOnly
  55. public void enableUntil(@NotNull Disposable disposable) {
  56. ENABLED = true;
  57. Disposer.register(disposable, ()-> ENABLED = false);
  58. }
  59. public void loadContext(@NotNull Element fromElement) {
  60. for (WorkingContextProvider provider : WorkingContextProvider.EP_NAME.getExtensionList()) {
  61. try {
  62. Element child = fromElement.getChild(provider.getId());
  63. if (child != null) {
  64. provider.loadContext(myProject, child);
  65. }
  66. }
  67. catch (InvalidDataException e) {
  68. LOG.error(e);
  69. }
  70. }
  71. }
  72. public void saveContext(Element toElement) {
  73. for (WorkingContextProvider provider : WorkingContextProvider.EP_NAME.getExtensionList()) {
  74. try {
  75. Element child = new Element(provider.getId());
  76. provider.saveContext(myProject, child);
  77. toElement.addContent(child);
  78. }
  79. catch (WriteExternalException e) {
  80. LOG.error(e);
  81. }
  82. }
  83. }
  84. public void clearContext() {
  85. for (WorkingContextProvider provider : WorkingContextProvider.EP_NAME.getExtensionList()) {
  86. provider.clearContext(myProject);
  87. }
  88. }
  89. public void saveContext(Task task) {
  90. String entryName = task.getId() + TASK_XML_POSTFIX;
  91. saveContext(entryName, TASKS_ZIP_POSTFIX, task.getSummary());
  92. }
  93. public void saveContext(@Nullable String entryName, @Nullable String comment) {
  94. saveContext(entryName, CONTEXT_ZIP_POSTFIX, comment);
  95. }
  96. public boolean hasContext(String entryName) {
  97. return doEntryAction(CONTEXT_ZIP_POSTFIX, entryName, entry -> {});
  98. }
  99. private synchronized void saveContext(@Nullable String entryName, String zipPostfix, @Nullable String comment) {
  100. if (!ENABLED) return;
  101. try (JBZipFile archive = getTasksArchive(zipPostfix)) {
  102. if (entryName == null) {
  103. int i = archive.getEntries().size();
  104. do {
  105. entryName = "context" + i++;
  106. } while (archive.getEntry("/" + entryName) != null);
  107. }
  108. JBZipEntry entry = archive.getOrCreateEntry("/" + entryName);
  109. if (comment != null) {
  110. entry.setComment(StringUtil.first(comment, 200, true));
  111. }
  112. Element element = new Element("context");
  113. saveContext(element);
  114. String s = new XMLOutputter().outputString(element);
  115. entry.setData(s.getBytes(StandardCharsets.UTF_8));
  116. }
  117. catch (IOException e) {
  118. LOG.error(e);
  119. }
  120. }
  121. private JBZipFile getTasksArchive(String postfix) {
  122. File file = getArchiveFile(postfix);
  123. try {
  124. return new JBZipFile(file);
  125. }
  126. catch (IOException e) {
  127. file.delete();
  128. JBZipFile zipFile = null;
  129. try {
  130. zipFile = new JBZipFile(file);
  131. Notifications.Bus.notify(new Notification("Tasks", "Context Data Corrupted",
  132. "Context information history for " + myProject.getName() + " was corrupted.\n" +
  133. "The history was replaced with empty one.", NotificationType.ERROR), myProject);
  134. }
  135. catch (IOException e1) {
  136. LOG.error("Can't repair form context data corruption", e1);
  137. }
  138. return zipFile;
  139. }
  140. }
  141. private File getArchiveFile(String postfix) {
  142. File tasksFolder = PathManager.getConfigDir().resolve(TASKS_FOLDER).toFile();
  143. if (!tasksFolder.exists()) {
  144. //noinspection ResultOfMethodCallIgnored
  145. tasksFolder.mkdirs();
  146. }
  147. String projectName = FileUtil.sanitizeFileName(myProject.getName());
  148. return new File(tasksFolder, projectName + postfix);
  149. }
  150. public void restoreContext(@NotNull Task task) {
  151. loadContext(TASKS_ZIP_POSTFIX, task.getId() + TASK_XML_POSTFIX);
  152. }
  153. private boolean loadContext(String zipPostfix, String entryName) {
  154. return doEntryAction(zipPostfix, entryName, entry -> {
  155. String s = new String(entry.getData(), StandardCharsets.UTF_8);
  156. loadContext(JDOMUtil.load(s));
  157. });
  158. }
  159. private synchronized boolean doEntryAction(String zipPostfix, String entryName, ThrowableConsumer<JBZipEntry, Exception> action) {
  160. if (!ENABLED) return false;
  161. try (JBZipFile archive = getTasksArchive(zipPostfix)) {
  162. JBZipEntry entry = archive.getEntry(StringUtil.startsWithChar(entryName, '/') ? entryName : "/" + entryName);
  163. if (entry != null) {
  164. action.consume(entry);
  165. return true;
  166. }
  167. }
  168. catch (Exception e) {
  169. LOG.error(e);
  170. }
  171. return false;
  172. }
  173. public List<ContextInfo> getContextHistory() {
  174. return getContextHistory(CONTEXT_ZIP_POSTFIX);
  175. }
  176. private synchronized List<ContextInfo> getContextHistory(String zipPostfix) {
  177. if (!ENABLED) return Collections.emptyList();
  178. try (JBZipFile archive = getTasksArchive(zipPostfix)) {
  179. List<JBZipEntry> entries = archive.getEntries();
  180. return ContainerUtil.mapNotNull(entries, (NullableFunction<JBZipEntry, ContextInfo>)entry -> entry.getName().startsWith("/context") ? new ContextInfo(entry.getName(), entry.getTime(), entry.getComment()) : null);
  181. }
  182. catch (Exception e) {
  183. LOG.error(e);
  184. return Collections.emptyList();
  185. }
  186. }
  187. public boolean loadContext(String name) {
  188. return loadContext(CONTEXT_ZIP_POSTFIX, name);
  189. }
  190. public void removeContext(String name) {
  191. removeContext(name, CONTEXT_ZIP_POSTFIX);
  192. }
  193. public void removeContext(Task task) {
  194. removeContext(task.getId(), TASKS_ZIP_POSTFIX);
  195. }
  196. private void removeContext(String name, String postfix) {
  197. if (!ENABLED) return;
  198. try (JBZipFile archive = getTasksArchive(postfix)) {
  199. JBZipEntry entry = archive.getEntry(name);
  200. if (entry != null) {
  201. archive.eraseEntry(entry);
  202. }
  203. }
  204. catch (IOException e) {
  205. LOG.error(e);
  206. }
  207. }
  208. public void pack(int max, int delta) {
  209. pack(max, delta, CONTEXT_ZIP_POSTFIX);
  210. pack(max, delta, TASKS_ZIP_POSTFIX);
  211. }
  212. private synchronized void pack(int max, int delta, String zipPostfix) {
  213. if (!ENABLED) return;
  214. try (JBZipFile archive = getTasksArchive(zipPostfix)) {
  215. List<JBZipEntry> entries = archive.getEntries();
  216. if (entries.size() > max + delta) {
  217. JBZipEntry[] array = entries.toArray(new JBZipEntry[0]);
  218. Arrays.sort(array, ENTRY_COMPARATOR);
  219. for (int i = array.length - 1; i >= max; i--) {
  220. archive.eraseEntry(array[i]);
  221. }
  222. archive.gc();
  223. }
  224. }
  225. catch (IOException e) {
  226. LOG.error(e);
  227. }
  228. }
  229. @TestOnly
  230. public File getContextFile() {
  231. return getArchiveFile(CONTEXT_ZIP_POSTFIX);
  232. }
  233. @TestOnly
  234. public File getTaskFile() {
  235. return getArchiveFile(TASKS_ZIP_POSTFIX);
  236. }
  237. }