EDU-547 Get rid of answer extension for Task Files
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / actions / CCCreateCourseArchive.java
1 package com.jetbrains.edu.coursecreator.actions;
2
3 import com.google.gson.Gson;
4 import com.google.gson.GsonBuilder;
5 import com.intellij.icons.AllIcons;
6 import com.intellij.ide.projectView.ProjectView;
7 import com.intellij.openapi.actionSystem.AnActionEvent;
8 import com.intellij.openapi.actionSystem.CommonDataKeys;
9 import com.intellij.openapi.actionSystem.LangDataKeys;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.diagnostic.Logger;
12 import com.intellij.openapi.module.Module;
13 import com.intellij.openapi.project.DumbAwareAction;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.ui.DialogWrapper;
16 import com.intellij.openapi.ui.Messages;
17 import com.intellij.openapi.util.Ref;
18 import com.intellij.openapi.util.io.FileUtil;
19 import com.intellij.openapi.util.io.FileUtilRt;
20 import com.intellij.openapi.vfs.VfsUtil;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.openapi.vfs.VirtualFileManager;
23 import com.intellij.util.containers.HashMap;
24 import com.intellij.util.io.ZipUtil;
25 import com.jetbrains.edu.EduNames;
26 import com.jetbrains.edu.EduUtils;
27 import com.jetbrains.edu.courseFormat.Course;
28 import com.jetbrains.edu.courseFormat.Lesson;
29 import com.jetbrains.edu.courseFormat.Task;
30 import com.jetbrains.edu.courseFormat.TaskFile;
31 import com.jetbrains.edu.coursecreator.CCLanguageManager;
32 import com.jetbrains.edu.coursecreator.CCProjectService;
33 import com.jetbrains.edu.coursecreator.CCUtils;
34 import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import java.io.*;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.zip.ZipOutputStream;
42
43 public class CCCreateCourseArchive extends DumbAwareAction {
44   private static final Logger LOG = Logger.getInstance(CCCreateCourseArchive.class.getName());
45   private String myZipName;
46   private String myLocationDir;
47
48   public void setZipName(String zipName) {
49     myZipName = zipName;
50   }
51
52   public void setLocationDir(String locationDir) {
53     myLocationDir = locationDir;
54   }
55
56   public CCCreateCourseArchive() {
57     super("Generate Course Archive", "Generate Course Archive", AllIcons.FileTypes.Archive);
58   }
59
60   @Override
61   public void update(@NotNull AnActionEvent e) {
62     CCProjectService.setCCActionAvailable(e);
63   }
64
65   @Override
66   public void actionPerformed(@NotNull AnActionEvent e) {
67     final Project project = e.getData(CommonDataKeys.PROJECT);
68     final Module module = e.getData(LangDataKeys.MODULE);
69     if (project == null || module == null) {
70       return;
71     }
72     createCourseArchive(project, module);
73   }
74
75   private void createCourseArchive(final Project project, Module module) {
76     final CCProjectService service = CCProjectService.getInstance(project);
77     final Course course = service.getCourse();
78     if (course == null) return;
79     CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this);
80     dlg.show();
81     if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
82       return;
83     }
84     final VirtualFile baseDir = project.getBaseDir();
85
86     VirtualFile archiveFolder = getArchiveFolder(project, module);
87     if (archiveFolder == null) {
88       return;
89     }
90
91     CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
92     if (manager == null) {
93       return;
94     }
95     FileFilter filter = new FileFilter() {
96       @Override
97       public boolean accept(File pathname) {
98         return !manager.doNotPackFile(pathname);
99       }
100     };
101
102     for (VirtualFile child : baseDir.getChildren()) {
103       String name = child.getName();
104       File fromFile = new File(child.getPath());
105       if (CCUtils.GENERATED_FILES_FOLDER.equals(name) || ".idea".equals(name)
106           || name.contains("iml") || manager.doNotPackFile(fromFile)) {
107         continue;
108       }
109       copyChild(archiveFolder, filter, child, fromFile);
110     }
111
112     final List<Lesson> lessons = course.getLessons();
113
114     ApplicationManager.getApplication().runWriteAction(new Runnable() {
115       @Override
116       public void run() {
117         final Map<TaskFile, TaskFile> savedTaskFiles = new HashMap<TaskFile, TaskFile>();
118         replaceAnswerFilesWithTaskFiles(savedTaskFiles);
119         generateJson(project, archiveFolder);
120         resetTaskFiles(savedTaskFiles);
121         VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
122         packCourse(archiveFolder);
123         synchronize(project);
124       }
125
126       private void replaceAnswerFilesWithTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
127         for (Lesson lesson : lessons) {
128           final VirtualFile lessonDir = baseDir.findChild(EduNames.LESSON + String.valueOf(lesson.getIndex()));
129           if (lessonDir == null) continue;
130           for (Task task : lesson.getTaskList()) {
131             final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(task.getIndex()));
132             if (taskDir == null) continue;
133             for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
134               TaskFile taskFileCopy = new TaskFile();
135               TaskFile taskFile = entry.getValue();
136               TaskFile.copy(taskFile, taskFileCopy);
137               savedTaskFiles.put(taskFile, taskFileCopy);
138
139               VirtualFile userFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDir.getName(), taskDir.getName());
140               if (userFileDir == null) {
141                 continue;
142               }
143               String taskFileName = entry.getKey();
144               EduUtils.createStudentFileFromAnswer(project, userFileDir, taskDir, taskFileName, taskFile);
145               VirtualFile answerFile = userFileDir.findChild(
146                 FileUtilRt.getNameWithoutExtension(taskFileName) + ".answer." + FileUtilRt.getExtension(taskFileName));
147               if (answerFile != null) {
148                 try {
149                   answerFile.delete(this);
150                 }
151                 catch (IOException e) {
152                   LOG.info(e);
153                 }
154               }
155             }
156           }
157         }
158       }
159     });
160
161
162   }
163
164   private static void copyChild(VirtualFile archiveFolder, FileFilter filter, VirtualFile child, File fromFile) {
165     File toFile = new File(archiveFolder.getPath(), child.getName());
166
167     try {
168       if (child.isDirectory()) {
169         FileUtil.copyDir(fromFile, toFile, filter);
170       }
171       else {
172         if (filter.accept(fromFile)) {
173           FileUtil.copy(fromFile, toFile);
174         }
175       }
176     }
177     catch (IOException e) {
178       LOG.info("Failed to copy" + fromFile.getPath(), e);
179     }
180   }
181
182   @Nullable
183   private VirtualFile getArchiveFolder(@NotNull Project project, @NotNull Module module) {
184     VirtualFile generatedFilesRoot = CCUtils.getGeneratedFilesFolder(project, module);
185     if (generatedFilesRoot == null) {
186       return null;
187     }
188     VirtualFile zipRoot = generatedFilesRoot.findChild(myZipName);
189     final Ref<VirtualFile> archiveFolder = new Ref<>();
190     ApplicationManager.getApplication().runWriteAction(new Runnable() {
191       @Override
192       public void run() {
193         try {
194           if (zipRoot != null) {
195             zipRoot.delete(this);
196           }
197           archiveFolder.set(generatedFilesRoot.createChildDirectory(this, myZipName));
198         }
199         catch (IOException e) {
200           LOG.error("Failed to get zip root for " + myZipName, e);
201         }
202       }
203     });
204     return archiveFolder.get();
205   }
206
207
208   private static void resetTaskFiles(Map<TaskFile, TaskFile> savedTaskFiles) {
209     for (Map.Entry<TaskFile, TaskFile> entry : savedTaskFiles.entrySet()) {
210       entry.getKey().setAnswerPlaceholders(entry.getValue().getAnswerPlaceholders());
211     }
212   }
213
214   private static void synchronize(@NotNull final Project project) {
215     VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
216     ProjectView.getInstance(project).refresh();
217   }
218
219   private void packCourse(@NotNull final VirtualFile baseDir) {
220     try {
221       final File zipFile = new File(myLocationDir, myZipName + ".zip");
222       ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
223       VirtualFile[] courseFiles = baseDir.getChildren();
224       for (VirtualFile file : courseFiles) {
225         ZipUtil.addFileOrDirRecursively(zos, null, new File(file.getPath()), file.getName(), null, null);
226       }
227       zos.close();
228       Messages.showInfoMessage("Course archive was saved to " + zipFile.getPath(), "Course Archive Was Created Successfully");
229     }
230     catch (IOException e1) {
231       LOG.error(e1);
232     }
233   }
234
235   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
236   private static void generateJson(@NotNull final Project project, VirtualFile parentDir) {
237     final CCProjectService service = CCProjectService.getInstance(project);
238     final Course course = service.getCourse();
239     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
240     final String json = gson.toJson(course);
241     final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE);
242     OutputStreamWriter outputStreamWriter = null;
243     try {
244       outputStreamWriter = new OutputStreamWriter(new FileOutputStream(courseJson), "UTF-8");
245       outputStreamWriter.write(json);
246     }
247     catch (Exception e) {
248       Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json");
249       LOG.info(e);
250     }
251     finally {
252       try {
253         if (outputStreamWriter != null) {
254           outputStreamWriter.close();
255         }
256       }
257       catch (IOException e1) {
258         //close silently
259       }
260     }
261   }
262 }