EDU-547 Get rid of answer extension for Task Files
authorLiana Bakradze <liana.bakradze@jetbrains.com>
Fri, 26 Feb 2016 10:01:45 +0000 (13:01 +0300)
committerLiana Bakradze <liana.bakradze@jetbrains.com>
Fri, 26 Feb 2016 10:05:05 +0000 (13:05 +0300)
Create special invisible excluded directory to perform all the replacement inside it

python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCFileDeletedListener.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java

index 03e400bd45461d7832e8d675f4da1d80ef594c6e..ae59f4f32462b10b3bd7631f2278e5936a550d2d 100644 (file)
@@ -25,6 +25,9 @@ class CCFileDeletedListener extends VirtualFileAdapter {
       return;
     }
     VirtualFile removedFile = event.getFile();
+    if (removedFile.getPath().contains(CCUtils.GENERATED_FILES_FOLDER)) {
+      return;
+    }
     final TaskFile taskFile = CCProjectService.getInstance(myProject).getTaskFile(removedFile);
     if (taskFile != null) {
       deleteAnswerFile(removedFile, taskFile);
index 36fda69ede933efd1651970597efbc5107e4e83e..16b71bbc0be1da3e664390378a8322684f37bace 100644 (file)
@@ -2,9 +2,18 @@ package com.jetbrains.edu.coursecreator;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.intellij.ide.projectView.actions.MarkRootActionBase;
 import com.intellij.lang.Language;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.DumbModePermission;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiElement;
@@ -24,6 +33,7 @@ import java.util.Comparator;
 
 public class CCUtils {
   private static final Logger LOG = Logger.getInstance(CCUtils.class);
+  public static final String GENERATED_FILES_FOLDER = ".coursecreator";
 
   @Nullable
   public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) {
@@ -103,4 +113,40 @@ public class CCUtils {
     }
     return false;
   }
+
+
+  public static VirtualFile getGeneratedFilesFolder(@NotNull Project project, @NotNull Module module) {
+    VirtualFile baseDir = project.getBaseDir();
+    VirtualFile folder = baseDir.findChild(GENERATED_FILES_FOLDER);
+    if (folder != null) {
+      return folder;
+    }
+    final Ref<VirtualFile> generatedRoot = new Ref<>();
+    DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
+      @Override
+      public void run() {
+        ApplicationManager.getApplication().runWriteAction(new Runnable() {
+          @Override
+          public void run() {
+            try {
+              generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER));
+              final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel();
+              ContentEntry entry = MarkRootActionBase.findContentEntry(model, generatedRoot.get());
+              if (entry == null) {
+                LOG.info("Failed to find contentEntry for archive folder");
+                return;
+              }
+              entry.addExcludeFolder(generatedRoot.get());
+              model.commit();
+              module.getProject().save();
+            }
+            catch (IOException e) {
+              LOG.info("Failed to create folder for generated files", e);
+            }
+          }
+        });
+      }
+    });
+    return generatedRoot.get();
+  }
 }
index 87f15abad5154cbf02557aba7d80cd306a9cf9b8..e47a1026cf9188218845061c43c3b401a072265f 100644 (file)
@@ -6,16 +6,21 @@ import com.intellij.icons.AllIcons;
 import com.intellij.ide.projectView.ProjectView;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.util.containers.hash.HashMap;
+import com.intellij.util.containers.HashMap;
 import com.intellij.util.io.ZipUtil;
 import com.jetbrains.edu.EduNames;
 import com.jetbrains.edu.EduUtils;
@@ -28,6 +33,7 @@ import com.jetbrains.edu.coursecreator.CCProjectService;
 import com.jetbrains.edu.coursecreator.CCUtils;
 import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
 import java.util.List;
@@ -59,13 +65,14 @@ public class CCCreateCourseArchive extends DumbAwareAction {
   @Override
   public void actionPerformed(@NotNull AnActionEvent e) {
     final Project project = e.getData(CommonDataKeys.PROJECT);
-    if (project == null) {
+    final Module module = e.getData(LangDataKeys.MODULE);
+    if (project == null || module == null) {
       return;
     }
-    createCourseArchive(project);
+    createCourseArchive(project, module);
   }
 
-  private void createCourseArchive(final Project project) {
+  private void createCourseArchive(final Project project, Module module) {
     final CCProjectService service = CCProjectService.getInstance(project);
     final Course course = service.getCourse();
     if (course == null) return;
@@ -75,36 +82,129 @@ public class CCCreateCourseArchive extends DumbAwareAction {
       return;
     }
     final VirtualFile baseDir = project.getBaseDir();
+
+    VirtualFile archiveFolder = getArchiveFolder(project, module);
+    if (archiveFolder == null) {
+      return;
+    }
+
+    CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
+    if (manager == null) {
+      return;
+    }
+    FileFilter filter = new FileFilter() {
+      @Override
+      public boolean accept(File pathname) {
+        return !manager.doNotPackFile(pathname);
+      }
+    };
+
+    for (VirtualFile child : baseDir.getChildren()) {
+      String name = child.getName();
+      File fromFile = new File(child.getPath());
+      if (CCUtils.GENERATED_FILES_FOLDER.equals(name) || ".idea".equals(name)
+          || name.contains("iml") || manager.doNotPackFile(fromFile)) {
+        continue;
+      }
+      copyChild(archiveFolder, filter, child, fromFile);
+    }
+
     final List<Lesson> lessons = course.getLessons();
 
-    final Map<TaskFile, TaskFile> savedTaskFiles = new HashMap<TaskFile, TaskFile>();
-    for (Lesson lesson : lessons) {
-      final VirtualFile lessonDir = baseDir.findChild(EduNames.LESSON + String.valueOf(lesson.getIndex()));
-      if (lessonDir == null) continue;
-      for (Task task : lesson.getTaskList()) {
-        final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(task.getIndex()));
-        if (taskDir == null) continue;
-        for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-          ApplicationManager.getApplication().runWriteAction(new Runnable() {
-            @Override
-            public void run() {
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        final Map<TaskFile, TaskFile> savedTaskFiles = new HashMap<TaskFile, TaskFile>();
+        replaceAnswerFilesWithTaskFiles(savedTaskFiles);
+        generateJson(project, archiveFolder);
+        resetTaskFiles(savedTaskFiles);
+        VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
+        packCourse(archiveFolder);
+        synchronize(project);
+      }
+
+      private void replaceAnswerFilesWithTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
+        for (Lesson lesson : lessons) {
+          final VirtualFile lessonDir = baseDir.findChild(EduNames.LESSON + String.valueOf(lesson.getIndex()));
+          if (lessonDir == null) continue;
+          for (Task task : lesson.getTaskList()) {
+            final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(task.getIndex()));
+            if (taskDir == null) continue;
+            for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
               TaskFile taskFileCopy = new TaskFile();
               TaskFile taskFile = entry.getValue();
               TaskFile.copy(taskFile, taskFileCopy);
               savedTaskFiles.put(taskFile, taskFileCopy);
-              EduUtils.createStudentFileFromAnswer(project, taskDir, taskDir, entry.getKey(), taskFile);
+
+              VirtualFile userFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDir.getName(), taskDir.getName());
+              if (userFileDir == null) {
+                continue;
+              }
+              String taskFileName = entry.getKey();
+              EduUtils.createStudentFileFromAnswer(project, userFileDir, taskDir, taskFileName, taskFile);
+              VirtualFile answerFile = userFileDir.findChild(
+                FileUtilRt.getNameWithoutExtension(taskFileName) + ".answer." + FileUtilRt.getExtension(taskFileName));
+              if (answerFile != null) {
+                try {
+                  answerFile.delete(this);
+                }
+                catch (IOException e) {
+                  LOG.info(e);
+                }
+              }
             }
-          });
+          }
         }
       }
+    });
+
+
+  }
+
+  private static void copyChild(VirtualFile archiveFolder, FileFilter filter, VirtualFile child, File fromFile) {
+    File toFile = new File(archiveFolder.getPath(), child.getName());
+
+    try {
+      if (child.isDirectory()) {
+        FileUtil.copyDir(fromFile, toFile, filter);
+      }
+      else {
+        if (filter.accept(fromFile)) {
+          FileUtil.copy(fromFile, toFile);
+        }
+      }
+    }
+    catch (IOException e) {
+      LOG.info("Failed to copy" + fromFile.getPath(), e);
     }
-    generateJson(project);
-    VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
-    packCourse(baseDir, course);
-    synchronize(project);
-    resetTaskFiles(savedTaskFiles);
   }
 
+  @Nullable
+  private VirtualFile getArchiveFolder(@NotNull Project project, @NotNull Module module) {
+    VirtualFile generatedFilesRoot = CCUtils.getGeneratedFilesFolder(project, module);
+    if (generatedFilesRoot == null) {
+      return null;
+    }
+    VirtualFile zipRoot = generatedFilesRoot.findChild(myZipName);
+    final Ref<VirtualFile> archiveFolder = new Ref<>();
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          if (zipRoot != null) {
+            zipRoot.delete(this);
+          }
+          archiveFolder.set(generatedFilesRoot.createChildDirectory(this, myZipName));
+        }
+        catch (IOException e) {
+          LOG.error("Failed to get zip root for " + myZipName, e);
+        }
+      }
+    });
+    return archiveFolder.get();
+  }
+
+
   private static void resetTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
     for (Map.Entry<TaskFile, TaskFile> entry : savedTaskFiles.entrySet()) {
       entry.getKey().setAnswerPlaceholders(entry.getValue().getAnswerPlaceholders());
@@ -116,25 +216,13 @@ public class CCCreateCourseArchive extends DumbAwareAction {
     ProjectView.getInstance(project).refresh();
   }
 
-  private void packCourse(@NotNull final VirtualFile baseDir, @NotNull final Course course) {
+  private void packCourse(@NotNull final VirtualFile baseDir) {
     try {
       final File zipFile = new File(myLocationDir, myZipName + ".zip");
       ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
-      final CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
       VirtualFile[] courseFiles = baseDir.getChildren();
       for (VirtualFile file : courseFiles) {
-        ZipUtil.addFileOrDirRecursively(zos, null, new File(file.getPath()), file.getName(), new FileFilter() {
-          @Override
-          public boolean accept(File pathname) {
-            String name = pathname.getName();
-            String nameWithoutExtension = FileUtil.getNameWithoutExtension(pathname);
-            if (nameWithoutExtension.endsWith(".answer") || name.contains(EduNames.WINDOWS_POSTFIX) || name.contains(".idea")
-              || FileUtil.filesEqual(pathname, zipFile)) {
-              return false;
-            }
-            return manager != null && !manager.doNotPackFile(pathname);
-          }
-        }, null);
+        ZipUtil.addFileOrDirRecursively(zos, null, new File(file.getPath()), file.getName(), null, null);
       }
       zos.close();
       Messages.showInfoMessage("Course archive was saved to " + zipFile.getPath(), "Course Archive Was Created Successfully");
@@ -145,12 +233,12 @@ public class CCCreateCourseArchive extends DumbAwareAction {
   }
 
   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
-  private static void generateJson(@NotNull final Project project) {
+  private static void generateJson(@NotNull final Project project, VirtualFile parentDir) {
     final CCProjectService service = CCProjectService.getInstance(project);
     final Course course = service.getCourse();
     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
     final String json = gson.toJson(course);
-    final File courseJson = new File(project.getBasePath(), EduNames.COURSE_META_FILE);
+    final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE);
     OutputStreamWriter outputStreamWriter = null;
     try {
       outputStreamWriter = new OutputStreamWriter(new FileOutputStream(courseJson), "UTF-8");
index d6e4c5347b62d4e1fccf099cea0859103502d95e..ea30845de4617a854d9cefe5bed0c18fce8756f1 100644 (file)
@@ -10,6 +10,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.jetbrains.edu.EduNames;
 import com.jetbrains.edu.coursecreator.CCProjectService;
+import com.jetbrains.edu.coursecreator.CCUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -30,6 +31,10 @@ public class CCTreeStructureProvider implements TreeStructureProvider, DumbAware
       Project project = node.getProject();
       if (project != null) {
         if (node.getValue() instanceof PsiDirectory) {
+          String name = ((PsiDirectory)node.getValue()).getName();
+          if (CCUtils.GENERATED_FILES_FOLDER.equals(name)) {
+            continue;
+          }
           PsiDirectory directory = (PsiDirectory)node.getValue();
           nodes.add(new CCDirectoryNode(project, directory, settings));
           continue;