added opportunity to create task files of all supported file types
[idea/community.git] / python / educational / 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.application.ApplicationManager;
10 import com.intellij.openapi.command.CommandProcessor;
11 import com.intellij.openapi.diagnostic.Logger;
12 import com.intellij.openapi.editor.Document;
13 import com.intellij.openapi.fileEditor.FileDocumentManager;
14 import com.intellij.openapi.project.DumbAwareAction;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.ui.DialogWrapper;
17 import com.intellij.openapi.ui.Messages;
18 import com.intellij.openapi.util.io.FileUtil;
19 import com.intellij.openapi.vfs.VirtualFile;
20 import com.intellij.openapi.vfs.VirtualFileManager;
21 import com.intellij.util.io.ZipUtil;
22 import com.jetbrains.edu.coursecreator.CCDocumentListener;
23 import com.jetbrains.edu.coursecreator.CCProjectService;
24 import com.jetbrains.edu.coursecreator.format.*;
25 import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog;
26 import org.jetbrains.annotations.NotNull;
27
28 import java.io.*;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.zip.ZipOutputStream;
33
34 public class CCCreateCourseArchive extends DumbAwareAction {
35   private static final Logger LOG = Logger.getInstance(CCCreateCourseArchive.class.getName());
36   private String myZipName;
37   private String myLocationDir;
38
39   public void setZipName(String zipName) {
40     myZipName = zipName;
41   }
42
43   public void setLocationDir(String locationDir) {
44     myLocationDir = locationDir;
45   }
46
47   public CCCreateCourseArchive() {
48     super("Generate Course Archive", "Generate Course Archive", AllIcons.FileTypes.Archive);
49   }
50
51   @Override
52   public void update(@NotNull AnActionEvent e) {
53     CCProjectService.setCCActionAvailable(e);
54   }
55
56   @Override
57   public void actionPerformed(@NotNull AnActionEvent e) {
58     final Project project = e.getData(CommonDataKeys.PROJECT);
59     if (project == null) {
60       return;
61     }
62     createCourseArchive(project);
63   }
64
65   private void createCourseArchive(final Project project) {
66     final CCProjectService service = CCProjectService.getInstance(project);
67     final Course course = service.getCourse();
68     if (course == null) return;
69     CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this);
70     dlg.show();
71     if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
72       return;
73     }
74     final VirtualFile baseDir = project.getBaseDir();
75     final Map<String, Lesson> lessons = course.getLessonsMap();
76     //map to store initial task file
77     final Map<TaskFile, TaskFile> taskFiles = new HashMap<TaskFile, TaskFile>();
78     for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) {
79       final VirtualFile lessonDir = baseDir.findChild(lesson.getKey());
80       if (lessonDir == null) continue;
81       for (Map.Entry<String, Task> task : lesson.getValue().myTasksMap.entrySet()) {
82         final VirtualFile taskDir = lessonDir.findChild(task.getKey());
83         if (taskDir == null) continue;
84         for (final Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) {
85           ApplicationManager.getApplication().runWriteAction(new Runnable() {
86             @Override
87             public void run() {
88               createUserFile(project, taskFiles, taskDir, taskDir, entry);
89             }
90           });
91         }
92       }
93     }
94     generateJson(project);
95     packCourse(baseDir, lessons);
96     resetTaskFiles(taskFiles);
97     synchronize(project);
98   }
99
100   public static void createUserFile(@NotNull final Project project,
101                                     @NotNull final Map<TaskFile, TaskFile> taskFilesCopy,
102                                     @NotNull final VirtualFile userFileDir,
103                                     @NotNull final VirtualFile answerFileDir,
104                                     @NotNull final Map.Entry<String, TaskFile> taskFiles) {
105     final String name = taskFiles.getKey();
106     VirtualFile file = userFileDir.findChild(name);
107     if (file != null) {
108       try {
109         file.delete(project);
110       }
111       catch (IOException e) {
112         LOG.error(e);
113       }
114     }
115     try {
116       userFileDir.createChildData(project, name);
117     }
118     catch (IOException e) {
119       LOG.error(e);
120     }
121
122     file = userFileDir.findChild(name);
123     assert file != null;
124     String answerFileName = file.getNameWithoutExtension() + ".answer." + file.getExtension();
125     VirtualFile answerFile = answerFileDir.findChild(answerFileName);
126     if (answerFile == null) {
127       return;
128     }
129     final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile);
130     if (answerDocument == null) {
131       return;
132     }
133     final Document document = FileDocumentManager.getInstance().getDocument(file);
134     if (document == null) return;
135     final TaskFile taskFile = taskFiles.getValue();
136     TaskFile taskFileSaved = new TaskFile();
137     taskFile.copy(taskFileSaved);
138     for (AnswerPlaceholder answerPlaceholder : taskFile.getTaskWindows()) {
139       answerPlaceholder.setLength(answerPlaceholder.getReplacementLength());
140     }
141     CommandProcessor.getInstance().executeCommand(project, new Runnable() {
142       @Override
143       public void run() {
144         ApplicationManager.getApplication().runWriteAction(new Runnable() {
145           @Override
146           public void run() {
147             document.replaceString(0, document.getTextLength(), answerDocument.getCharsSequence());
148           }
149         });
150       }
151     }, "x", "qwe");
152     InsertionListener listener = new InsertionListener(taskFile);
153     document.addDocumentListener(listener);
154     taskFilesCopy.put(taskFile, taskFileSaved);
155     Collections.sort(taskFile.getTaskWindows());
156     for (int i = taskFile.getTaskWindows().size() - 1; i >= 0; i--) {
157       final AnswerPlaceholder answerPlaceholder = taskFile.getTaskWindows().get(i);
158       replaceTaskWindow(project, document, answerPlaceholder);
159     }
160     CommandProcessor.getInstance().executeCommand(project, new Runnable() {
161       @Override
162       public void run() {
163         ApplicationManager.getApplication().runWriteAction(new Runnable() {
164           @Override
165           public void run() {
166             FileDocumentManager.getInstance().saveDocument(document);
167           }
168         });
169       }
170     }, "x", "qwe");
171     document.removeDocumentListener(listener);
172   }
173
174   private static void replaceTaskWindow(@NotNull final Project project,
175                                         @NotNull final Document document,
176                                         @NotNull final AnswerPlaceholder answerPlaceholder) {
177     final String taskText = answerPlaceholder.getTaskText();
178     final int lineStartOffset = document.getLineStartOffset(answerPlaceholder.getLine());
179     final int offset = lineStartOffset + answerPlaceholder.getStart();
180     CommandProcessor.getInstance().executeCommand(project, new Runnable() {
181       @Override
182       public void run() {
183         ApplicationManager.getApplication().runWriteAction(new Runnable() {
184           @Override
185           public void run() {
186             document.replaceString(offset, offset + answerPlaceholder.getLength(), taskText);
187             FileDocumentManager.getInstance().saveDocument(document);
188           }
189         });
190       }
191     }, "x", "qwe");
192   }
193
194   private static void synchronize(@NotNull final Project project) {
195     VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
196     ProjectView.getInstance(project).refresh();
197   }
198
199   public static void resetTaskFiles(@NotNull final Map<TaskFile, TaskFile> taskFiles) {
200     for (Map.Entry<TaskFile, TaskFile> entry : taskFiles.entrySet()) {
201       TaskFile realTaskFile = entry.getKey();
202       TaskFile savedTaskFile = entry.getValue();
203       realTaskFile.update(savedTaskFile);
204     }
205   }
206
207   private void packCourse(@NotNull final VirtualFile baseDir, @NotNull final Map<String, Lesson> lessons) {
208     try {
209       File zipFile = new File(myLocationDir, myZipName + ".zip");
210       ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
211
212       for (Map.Entry<String, Lesson> entry : lessons.entrySet()) {
213         final VirtualFile lessonDir = baseDir.findChild(entry.getKey());
214         if (lessonDir == null) continue;
215         ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), new FileFilter() {
216           @Override
217           public boolean accept(File pathname) {
218             String name = pathname.getName();
219             String nameWithoutExtension = FileUtil.getNameWithoutExtension(pathname);
220             return !nameWithoutExtension.endsWith(".answer") && !name.contains("__pycache__") && !name.contains("_windows") && !name.contains(".pyc");
221           }
222         }, null);
223       }
224       ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "hints"), "hints", null, null);
225       ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "course.json"), "course.json", null, null);
226       ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "test_helper.py"), "test_helper.py", null, null);
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) {
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(project.getBasePath(), "course.json");
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
263   public static class InsertionListener extends CCDocumentListener {
264
265     public InsertionListener(TaskFile taskFile) {
266       super(taskFile);
267     }
268
269     @Override
270     protected boolean useLength() {
271       return true;
272     }
273   }
274 }