diff --git a/python/educational-core/resources/META-INF/plugin.xml b/python/educational-core/resources/META-INF/plugin.xml index d1523e23db1d..941567b8c6ee 100644 --- a/python/educational-core/resources/META-INF/plugin.xml +++ b/python/educational-core/resources/META-INF/plugin.xml @@ -198,7 +198,6 @@ - \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCCheckListener.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCCheckListener.java deleted file mode 100644 index 12aa92541496..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCCheckListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyCheckListener; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public class CCCheckListener implements StudyCheckListener { - - @Override - public void beforeCheck(@NotNull Project project, @NotNull Task task) { - if (!CCUtils.isCourseCreator(project)) { - return; - } - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - CCUtils.updateResources(project, task, taskDir); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java index f5c081335c40..61d0323f968c 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java @@ -1,7 +1,6 @@ package com.jetbrains.edu.coursecreator; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -15,12 +14,11 @@ import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -47,9 +45,6 @@ public class CCProjectComponent extends AbstractProjectComponent { CCProjectService.getInstance(myProject).setCourse(null); oldCourse.initCourse(true); oldCourse.setCourseMode(CCUtils.COURSE_MODE); - File coursesDir = new File(PathManager.getConfigPath(), "courses"); - File courseDir = new File(coursesDir, oldCourse.getName() + "-" + myProject.getName()); - oldCourse.setCourseDirectory(courseDir.getPath()); StudyUtils.registerStudyToolWindow(oldCourse, myProject); transformFiles(oldCourse, myProject); } diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java index b9d46aa47d06..a917b185b31c 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java @@ -15,10 +15,7 @@ */ package com.jetbrains.edu.coursecreator; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -30,16 +27,14 @@ import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.util.Map; public class CCRefactoringElementListenerProvider implements RefactoringElementListenerProvider { - private static final Logger LOG = Logger.getInstance(CCRefactoringElementListenerProvider.class); @Nullable @Override @@ -94,21 +89,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL if (taskFile == null) { return; } - ApplicationManager.getApplication().runWriteAction(() -> { - VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, oldName); - if (patternFile != null) { - try { - patternFile.delete(CCRefactoringElementListenerProvider.class); - } - catch (IOException e) { - LOG.info(e); - } - } - }); - taskFiles.remove(oldName); taskFiles.put(StudyUtils.pathRelativeToTask(file.getVirtualFile()), taskFile); - CCUtils.createResourceFile(file.getVirtualFile(), course, taskDir.getVirtualFile()); } @Override diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java index c12b680a292b..7ec7d188972b 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java @@ -8,34 +8,37 @@ import com.intellij.openapi.actionSystem.Presentation; 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.ModuleRootModificationUtil; import com.intellij.openapi.roots.ProjectRootManager; 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.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.psi.PsiDirectory; import com.intellij.util.Function; +import com.intellij.util.containers.hash.HashMap; import com.jetbrains.edu.learning.EduPluginConfigurator; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.StudyItem; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; +import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; public class CCUtils { public static final String ANSWER_EXTENSION_DOTTED = ".answer."; @@ -137,27 +140,22 @@ public class CCUtils { return folder; } final Ref generatedRoot = new Ref<>(); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER)); - VirtualFile contentRootForFile = - ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get()); - if (contentRootForFile == null) { - return; - } - ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), - Collections.singletonList(generatedRoot.get().getUrl())); - } - catch (IOException e) { - LOG.info("Failed to create folder for generated files", e); - } + try { + generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER)); + VirtualFile contentRootForFile = + ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get()); + if (contentRootForFile == null) { + return; } - }); + ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), + Collections.singletonList(generatedRoot.get().getUrl())); + } + catch (IOException e) { + LOG.info("Failed to create folder for generated files", e); + } } }); return generatedRoot.get(); @@ -211,63 +209,6 @@ public class CCUtils { return configurator.isTestFile(file); } - public static void createResourceFile(VirtualFile createdFile, Course course, VirtualFile taskVF) { - VirtualFile lessonVF = taskVF.getParent(); - if (lessonVF == null) { - return; - } - - String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskVF.getName()); - File taskResourceFile = new File(taskResourcesPath); - if (!taskResourceFile.exists()) { - if (!taskResourceFile.mkdirs()) { - LOG.info("Failed to create resources for task " + taskResourcesPath); - } - } - try { - File toFile = new File(taskResourceFile, createdFile.getName()); - FileUtil.copy(new File(createdFile.getPath()), toFile); - } - catch (IOException e) { - LOG.info("Failed to copy created task file to resources " + createdFile.getPath()); - } - } - - - public static void updateResources(Project project, Task task, VirtualFile taskDir) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - VirtualFile lessonVF = taskDir.getParent(); - if (lessonVF == null) { - return; - } - - String taskResourcesPath = FileUtil.join(course.getCourseDirectory(), lessonVF.getName(), taskDir.getName()); - File taskResourceFile = new File(taskResourcesPath); - if (!taskResourceFile.exists()) { - if (!taskResourceFile.mkdirs()) { - LOG.info("Failed to create resources for task " + taskResourcesPath); - } - } - VirtualFile studentDir = LocalFileSystem.getInstance().findFileByIoFile(taskResourceFile); - if (studentDir == null) { - return; - } - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - VirtualFile answerFile = taskDir.findFileByRelativePath(name); - if (answerFile == null) { - continue; - } - ApplicationManager.getApplication().runWriteAction(() -> { - EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null, - task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0); - }); - } - } - public static void updateActionGroup(AnActionEvent e) { Presentation presentation = e.getPresentation(); Project project = e.getProject(); @@ -315,4 +256,58 @@ public class CCUtils { } }); } + + @Nullable + public static Lesson createAdditionalLesson(Course course, Project project) { + final VirtualFile baseDir = project.getBaseDir(); + EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); + + final Lesson lesson = new Lesson(); + lesson.setName(EduNames.PYCHARM_ADDITIONAL); + final Task task = new Task(); + task.setLesson(lesson); + task.setName(EduNames.PYCHARM_ADDITIONAL); + task.setIndex(1); + + VfsUtilCore.visitChildrenRecursively(baseDir, new VirtualFileVisitor(VirtualFileVisitor.NO_FOLLOW_SYMLINKS) { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + final String name = file.getName(); + if (name.equals(EduNames.COURSE_META_FILE) || name.equals(EduNames.HINTS) || name.startsWith(".")) return false; + String sanitizedName = FileUtil.sanitizeFileName(course.getName()); + final String archiveName = sanitizedName.startsWith("_") ? EduNames.COURSE : sanitizedName; + if (name.equals(archiveName + ".zip")) return false; + if (GENERATED_FILES_FOLDER.equals(name) || Project.DIRECTORY_STORE_FOLDER.equals(name)) { + return false; + } + if (file.isDirectory()) return true; + + if (StudyUtils.isTaskDescriptionFile(name) || StudyUtils.isTestsFile(project, name)) return true; + + if (name.contains(".iml") || (configurator != null && configurator.excludeFromArchive(name))) { + return false; + } + final TaskFile taskFile = StudyUtils.getTaskFile(project, file); + if (taskFile == null) { + final String path = VfsUtilCore.getRelativePath(file, baseDir); + try { + if (EduUtils.isImage(file.getName())) { + task.addTestsTexts(path, Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()))); + } + else { + task.addTestsTexts(path, FileUtil.loadTextAndClose(file.getInputStream())); + } + } + catch (IOException e) { + LOG.error("Can't find file " + path); + } + } + return true; + } + }); + if (task.getTestsText().isEmpty()) return null; + lesson.addTask(task); + lesson.setIndex(1); + return lesson; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java index a45193c712ca..52b81d8f222c 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java @@ -48,7 +48,7 @@ public class CCVirtualFileListener implements VirtualFileListener { String taskRelativePath = StudyUtils.pathRelativeToTask(createdFile); EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator != null && configurator.excludeFromArchive(new File(createdFile.getPath()))) { + if (configurator != null && configurator.excludeFromArchive(createdFile.getName())) { return; } @@ -68,8 +68,6 @@ public class CCVirtualFileListener implements VirtualFileListener { return; } - CCUtils.createResourceFile(createdFile, course, taskVF); - task.addTaskFile(taskRelativePath, 1); } @@ -89,7 +87,7 @@ public class CCVirtualFileListener implements VirtualFileListener { return; } Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || path.contains(FileUtil.toSystemIndependentName(course.getCourseDirectory()))) { + if (course == null) { return; } final TaskFile taskFile = StudyUtils.getTaskFile(project, removedFile); @@ -112,7 +110,7 @@ public class CCVirtualFileListener implements VirtualFileListener { } VirtualFile courseDir = project.getBaseDir(); CCUtils.updateHigherElements(courseDir.getChildren(), file -> course.getLesson(file.getName()), removedLesson.getIndex(), EduNames.LESSON, -1); - course.getLessons().remove(removedLesson); + course.removeLesson(removedLesson); } private static void deleteTask(@NotNull final Course course, @NotNull final VirtualFile removedTask) { diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java index a1bf760c5a6a..9b9904b0f883 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java @@ -9,8 +9,8 @@ import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; public class CCAddAsTaskFile extends CCTaskFileActionBase { public static final String ACTION_NAME = "Make Visible to Student"; @@ -21,7 +21,7 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase { protected void performAction(VirtualFile file, Task task, Course course, Project project) { - EduUtils.runUndoableAction(project, ACTION_NAME, new AddTaskFile(file, null, course, project, task)); + EduUtils.runUndoableAction(project, ACTION_NAME, new AddTaskFile(file, null, project, task)); } protected boolean isAvailable(Project project, VirtualFile file) { @@ -31,21 +31,20 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase { private static class AddTaskFile extends BasicUndoableAction { private final VirtualFile myFile; private TaskFile myTaskFile; - private final Course myCourse; private final Project myProject; private final Task myTask; - public AddTaskFile(VirtualFile file, TaskFile taskFile, Course course, Project project, Task task) { + public AddTaskFile(VirtualFile file, TaskFile taskFile, Project project, Task task) { super(file); myFile = file; myTaskFile = taskFile; - myCourse = course; myProject = project; myTask = task; } @Override public void undo() throws UnexpectedUndoException { + if (myTaskFile == null) return; CCHideFromStudent.hideFromStudent(myFile, myProject, myTask.getTaskFiles(), myTaskFile); ProjectView.getInstance(myProject).refresh(); } @@ -59,7 +58,6 @@ public class CCAddAsTaskFile extends CCTaskFileActionBase { myTask.addTaskFile(taskRelativePath, myTask.getTaskFiles().size()); myTaskFile = myTask.getTaskFile(taskRelativePath); } - CCUtils.createResourceFile(myFile, myCourse, StudyUtils.getTaskDir(myFile)); ProjectView.getInstance(myProject).refresh(); } diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java index a199f2d0ebc1..8703e3612117 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java @@ -14,30 +14,31 @@ 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.Condition; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; import com.intellij.util.io.ZipUtil; import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog; import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import org.jetbrains.annotations.NotNull; import java.io.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import java.util.zip.ZipOutputStream; public class CCCreateCourseArchive extends DumbAwareAction { @@ -90,34 +91,26 @@ public class CCCreateCourseArchive extends DumbAwareAction { return; } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return; - } - FileFilter filter = pathname -> !configurator.excludeFromArchive(pathname); - - for (VirtualFile child : baseDir.getChildren()) { - String name = child.getName(); - File fromFile = new File(child.getPath()); - if (CCUtils.GENERATED_FILES_FOLDER.equals(name) || Project.DIRECTORY_STORE_FOLDER.equals(name) - || name.contains("iml") || configurator.excludeFromArchive(fromFile)) { - continue; - } - copyChild(archiveFolder, filter, child, fromFile); - } - ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { archiveFolder.refresh(false, true); Course courseCopy = course.copy(); replaceAnswerFilesWithTaskFiles(courseCopy); + additionalFiles(courseCopy); generateJson(archiveFolder, courseCopy); VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); packCourse(archiveFolder, locationDir, zipName, showMessage); synchronize(project); } + private void additionalFiles(Course course) { + final Lesson lesson = CCUtils.createAdditionalLesson(course, project); + if (lesson != null) { + course.addLesson(lesson); + } + } + private void replaceAnswerFilesWithTaskFiles(Course courseCopy) { for (Lesson lesson : courseCopy.getLessons()) { String lessonDirName = EduNames.LESSON + String.valueOf(lesson.getIndex()); @@ -126,61 +119,76 @@ public class CCCreateCourseArchive extends DumbAwareAction { for (Task task : lesson.getTaskList()) { final VirtualFile taskDir = task.getTaskDir(project); if (taskDir == null) continue; - String taskDirName = EduNames.TASK + String.valueOf(task.getIndex()); - VirtualFile studentFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDirName, taskDirName); - if (studentFileDir == null) { - continue; - } - VirtualFile srcDir = studentFileDir.findChild(EduNames.SRC); - if (srcDir != null) { - studentFileDir = srcDir; - } - if (task instanceof TaskWithSubtasks) { - transformSubtaskTestsToTextFiles(studentFileDir); - } - for (String taskFile : task.getTaskFiles().keySet()) { - VirtualFile answerFile = taskDir.findFileByRelativePath(taskFile); - if (answerFile == null) { - continue; - } - EduUtils.createStudentFile(this, project, answerFile, studentFileDir, task, 0); - } + convertToStudentTaskFiles(task, taskDir); + addTestsToTask(task); + addDescriptions(task); } } } - private void transformSubtaskTestsToTextFiles(VirtualFile studentFileDir) { - Condition isSubtaskTestFile = - file -> CCUtils.isTestsFile(project, file) && file.getName().contains(EduNames.SUBTASK_MARKER); - List subtaskTests = ContainerUtil.filter(Arrays.asList(studentFileDir.getChildren()), isSubtaskTestFile); - for (VirtualFile subtaskTest : subtaskTests) { + private void convertToStudentTaskFiles(Task task, VirtualFile taskDir) { + final HashMap studentTaskFiles = new HashMap<>(); + for (Map.Entry entry : task.getTaskFiles().entrySet()) { + VirtualFile answerFile = taskDir.findFileByRelativePath(entry.getKey()); + if (answerFile == null) { + continue; + } + final TaskFile studentFile = EduUtils.createStudentFile(project, answerFile, task, 0); + if (studentFile != null) { + studentTaskFiles.put(entry.getKey(), studentFile); + } + } + task.taskFiles = studentTaskFiles; + } + + private void addDescriptions(@NotNull final Task task) { + final List descriptions = getDescriptionFiles(task, project); + for (VirtualFile file : descriptions) { try { - subtaskTest.rename(this, subtaskTest.getNameWithoutExtension() + ".txt"); + task.addTaskText(file.getName(), VfsUtilCore.loadText(file)); } catch (IOException e) { - LOG.error(e); + LOG.warn("Failed to load text " + file.getName()); } } } - }); - } - 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); + private void addTestsToTask(Task task) { + final List testFiles = getTestFiles(task, project); + for (VirtualFile file : testFiles) { + try { + task.addTestsTexts(file.getName(), VfsUtilCore.loadText(file)); + } + catch (IOException e) { + LOG.warn("Failed to load text " + file.getName()); + } } } - } - catch (IOException e) { - LOG.info("Failed to copy" + fromFile.getPath(), e); - } + + private List getTestFiles(@NotNull Task task, @NotNull Project project) { + List testFiles = new ArrayList<>(); + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return testFiles; + } + testFiles.addAll(Arrays.stream(taskDir.getChildren()) + .filter(file -> StudyUtils.isTestsFile(project, file.getName())) + .collect(Collectors.toList())); + return testFiles; + } + + private List getDescriptionFiles(@NotNull Task task, @NotNull Project project) { + List testFiles = new ArrayList<>(); + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return testFiles; + } + testFiles.addAll(Arrays.stream(taskDir.getChildren()) + .filter(file -> StudyUtils.isTaskDescriptionFile(file.getName())) + .collect(Collectors.toList())); + return testFiles; + } + }); } private static void synchronize(@NotNull final Project project) { diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java index 6949ff558c5f..11334cf12bdf 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java @@ -27,7 +27,10 @@ import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.core.EduDocumentListener; import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import org.jetbrains.annotations.NotNull; @@ -35,8 +38,6 @@ import java.io.*; import java.util.ArrayList; import java.util.Map; -import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR; - public class CCFromCourseArchive extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(CCFromCourseArchive.class.getName()); @@ -80,8 +81,6 @@ public class CCFromCourseArchive extends DumbAwareAction { } StudyTaskManager.getInstance(project).setCourse(course); - File courseDir = new File(OUR_COURSES_DIR, course.getName() + "-" + project.getName()); - course.setCourseDirectory(courseDir.getPath()); course.setCourseMode(CCUtils.COURSE_MODE); project.getBaseDir().refresh(false, true); int index = 1; diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCGetCourseFromStepic.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCGetCourseFromStepic.java index 5c16882a183a..957e47954511 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCGetCourseFromStepic.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCGetCourseFromStepic.java @@ -15,25 +15,22 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; import com.jetbrains.edu.learning.courseFormat.CourseInfo; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.util.Map; import static com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive.createAnswerFile; -import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR; -import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse; public class CCGetCourseFromStepic extends DumbAwareAction { @@ -66,21 +63,17 @@ public class CCGetCourseFromStepic extends DumbAwareAction { final Course course = EduStepicConnector.getCourse(project, info); if (course != null) { - flushCourse(course); - final File courseDirectory = StudyUtils.getCourseDirectory(course); ApplicationManager.getApplication().invokeAndWait(() -> ApplicationManager.getApplication().runWriteAction(() -> { final VirtualFile[] children = baseDir.getChildren(); for (VirtualFile child : children) { StudyUtils.deleteFile(child); } - StudyGenerator.createCourse(course, baseDir, courseDirectory, project); + StudyGenerator.createCourse(course, baseDir); })); StudyTaskManager.getInstance(project).setCourse(course); - File courseDir = new File(OUR_COURSES_DIR, course.getName() + "-" + project.getName()); - course.setCourseDirectory(courseDir.getPath()); course.setCourseMode(CCUtils.COURSE_MODE); project.getBaseDir().refresh(false, true); int index = 1; diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java index 7e13155c0425..accb05906ebf 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java @@ -1,29 +1,24 @@ package com.jetbrains.edu.coursecreator.actions; import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.undo.BasicUndoableAction; import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.util.Map; public class CCHideFromStudent extends CCTaskFileActionBase { - - private static final Logger LOG = Logger.getInstance(CCHideFromStudent.class); private static final String ACTION_NAME = "Hide from Student"; public CCHideFromStudent() { @@ -36,21 +31,19 @@ public class CCHideFromStudent extends CCTaskFileActionBase { if (taskFile == null) { return; } - EduUtils.runUndoableAction(project, ACTION_NAME, new HideTaskFile(project, course, file, task, taskFile)); + EduUtils.runUndoableAction(project, ACTION_NAME, new HideTaskFile(project, file, task, taskFile)); } private static class HideTaskFile extends BasicUndoableAction { private final Project myProject; - private final Course myCourse; private final VirtualFile myFile; private final Task myTask; private final TaskFile myTaskFile; - public HideTaskFile(Project project, Course course, VirtualFile file, Task task, TaskFile taskFile) { + public HideTaskFile(Project project, VirtualFile file, Task task, TaskFile taskFile) { super(file); myProject = project; - myCourse = course; myFile = file; myTask = task; myTaskFile = taskFile; @@ -59,7 +52,6 @@ public class CCHideFromStudent extends CCTaskFileActionBase { @Override public void undo() throws UnexpectedUndoException { myTask.getTaskFiles().put(StudyUtils.pathRelativeToTask(myFile), myTaskFile); - CCUtils.createResourceFile(myFile, myCourse, StudyUtils.getTaskDir(myFile)); if (!myTaskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(myProject).isFileOpen(myFile)) { for (FileEditor fileEditor : FileEditorManager.getInstance(myProject).getEditors(myFile)) { if (fileEditor instanceof TextEditor) { @@ -83,7 +75,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase { } } - public static void hideFromStudent(VirtualFile file, Project project, Map taskFiles, TaskFile taskFile) { + public static void hideFromStudent(VirtualFile file, Project project, Map taskFiles, @NotNull final TaskFile taskFile) { if (!taskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(project).isFileOpen(file)) { for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(file)) { if (fileEditor instanceof TextEditor) { @@ -93,17 +85,6 @@ public class CCHideFromStudent extends CCTaskFileActionBase { } } String taskRelativePath = StudyUtils.pathRelativeToTask(file); - VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, taskRelativePath); - ApplicationManager.getApplication().runWriteAction(() -> { - if (patternFile != null) { - try { - patternFile.delete(CCHideFromStudent.class); - } - catch (IOException e) { - LOG.info(e); - } - } - }); taskFiles.remove(taskRelativePath); } diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushCourse.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushCourse.java index 2ac39ca62623..156d2f477c4a 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushCourse.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushCourse.java @@ -11,12 +11,12 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.CourseInfo; import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; -import com.jetbrains.edu.learning.courseFormat.CourseInfo; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -59,11 +59,11 @@ public class CCPushCourse extends DumbAwareAction { public void run(@NotNull ProgressIndicator indicator) { for (Lesson lesson : course.getLessons()) { if (lesson.getId() > 0) { - CCStepicConnector.updateLesson(project, lesson, indicator); + CCStepicConnector.updateLesson(project, lesson); } else { final CourseInfo info = CourseInfo.fromCourse(course); - final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator); + final int lessonId = CCStepicConnector.postLesson(project, lesson); if (lessonId != -1) { final List sections = info.getSections(); final Integer sectionId = sections.get(sections.size() - 1); diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java index 3eda00f494fa..1c203cf6e85c 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPushLesson.java @@ -12,11 +12,11 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDirectory; import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; import com.jetbrains.edu.learning.courseFormat.CourseInfo; +import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.stepic.EduStepicNames; import org.jetbrains.annotations.NotNull; @@ -77,11 +77,11 @@ public class CCPushLesson extends DumbAwareAction { public void run(@NotNull ProgressIndicator indicator) { indicator.setText("Uploading lesson to " + EduStepicNames.STEPIC_URL); if (lesson.getId() > 0) { - CCStepicConnector.updateLesson(project, lesson, indicator); + CCStepicConnector.updateLesson(project, lesson); } else { final CourseInfo info = CourseInfo.fromCourse(course); - final int lessonId = CCStepicConnector.postLesson(project, lesson, indicator); + final int lessonId = CCStepicConnector.postLesson(project, lesson); final List sections = info.getSections(); final Integer sectionId = sections.get(sections.size()-1); CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId); diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java index 896aa1e5d2fb..067a0b47d443 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java @@ -31,10 +31,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.FrameWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; +import com.intellij.testFramework.LightVirtualFile; import com.intellij.ui.JBColor; import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.learning.StudyTaskManager; @@ -109,33 +109,24 @@ public class CCShowPreview extends DumbAwareAction { return; } - if (taskFile.getActivePlaceholders().isEmpty()) { Messages.showInfoMessage("Preview is available for task files with answer placeholders only", "No Preview for This File"); return; } - VirtualFile generatedFilesFolder = CCUtils.getGeneratedFilesFolder(project, module); - - if (generatedFilesFolder == null) { - return; - } final Task task = taskFile.getTask(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - Pair pair = - EduUtils.createStudentFile(this, project, virtualFile, generatedFilesFolder, null, - task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0); - if (pair != null) { - showPreviewDialog(project, pair.getFirst(), pair.getSecond()); - } + ApplicationManager.getApplication().runWriteAction(() -> { + TaskFile studentTaskFile = EduUtils.createStudentFile(project, virtualFile, task.copy(), + task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0); + if (studentTaskFile != null) { + showPreviewDialog(project, studentTaskFile); } }); } - private static void showPreviewDialog(@NotNull Project project, @NotNull VirtualFile userFile, @NotNull TaskFile taskFile) { + private static void showPreviewDialog(@NotNull Project project, @NotNull TaskFile taskFile) { final FrameWrapper showPreviewFrame = new FrameWrapper(project); + final LightVirtualFile userFile = new LightVirtualFile(taskFile.name, taskFile.text); showPreviewFrame.setTitle(userFile.getName()); LabeledEditor labeledEditor = new LabeledEditor(null); final EditorFactory factory = EditorFactory.getInstance(); diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java index 927cb26a4548..b8fc3e83c486 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java @@ -28,7 +28,6 @@ import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel; import com.jetbrains.edu.learning.EduPluginConfigurator; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.intellij.generation.EduCourseModuleBuilder; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; @@ -36,7 +35,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.text.JTextComponent; -import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.function.Function; @@ -93,8 +91,6 @@ class EduCCModuleBuilder extends EduCourseModuleBuilder { Language language = wrapper.getLanguage(); course.setLanguage(language.getID()); course.setCourseMode(CCUtils.COURSE_MODE); - File courseDir = new File(StudyProjectGenerator.OUR_COURSES_DIR, myPanel.getName() + "-" + project.getName()); - course.setCourseDirectory(courseDir.getPath()); StudyTaskManager.getInstance(project).setCourse(course); EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); String languageName = language.getDisplayName(); diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java index 6f5e3cd7768b..85b018c7df81 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java @@ -19,15 +19,13 @@ public class CCOptions implements StudyOptionsProvider { if (!StudySettings.getInstance().isCourseCreatorEnabled()) return null; if (CCSettings.getInstance().useHtmlAsDefaultTaskFormat()) { myHtmlRadioButton.setSelected(true); - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { - IdeFocusManager.getGlobalInstance().requestFocus(myHtmlRadioButton, true); - }); + IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown( + () -> IdeFocusManager.getGlobalInstance().requestFocus(myHtmlRadioButton, true)); } else { myMarkdownRadioButton.setSelected(true); - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { - IdeFocusManager.getGlobalInstance().requestFocus(myMarkdownRadioButton, true); - }); + IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown( + () -> IdeFocusManager.getGlobalInstance().requestFocus(myMarkdownRadioButton, true)); } return myPanel; } diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java index 1b45fb108668..d052e1bbbe51 100644 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java +++ b/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java @@ -8,13 +8,9 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileFilter; +import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.CourseInfo; @@ -24,7 +20,6 @@ import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient; import com.jetbrains.edu.learning.stepic.EduStepicNames; import com.jetbrains.edu.learning.stepic.StepicUser; import com.jetbrains.edu.learning.stepic.StepicWrappers; -import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; @@ -65,13 +60,16 @@ public class CCStepicConnector { ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) { @Override public void run(@NotNull final ProgressIndicator indicator) { - postCourse(project, course, indicator); + postCourse(project, course); } }); } - private static void postCourse(final Project project, @NotNull Course course, @NotNull final ProgressIndicator indicator) { - indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL); + private static void postCourse(final Project project, @NotNull Course course) { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL); + } final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses"); final StepicUser currentUser = EduStepicAuthorizedClient.getCurrentUser(); @@ -110,55 +108,25 @@ public class CCStepicConnector { final int sectionId = postModule(postedCourse.getId(), 1, String.valueOf(postedCourse.getName())); int position = 1; for (Lesson lesson : course.getLessons()) { - indicator.checkCanceled(); - final int lessonId = postLesson(project, lesson, indicator); + if (indicator != null) { + indicator.checkCanceled(); + } + final int lessonId = postLesson(project, lesson); postUnit(lessonId, position, sectionId); position += 1; } - ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.getId(), indicator)); + ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(course, project, postedCourse.getId())); } catch (IOException e) { LOG.error(e.getMessage()); } } - private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) { - final VirtualFile baseDir = project.getBaseDir(); - final List files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() { - @Override - public boolean accept(VirtualFile file) { - final String name = file.getName(); - return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) && - !"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith("."); - } - }); - - if (!files.isEmpty()) { + private static void postAdditionalFiles(Course course, @NotNull final Project project, int id) { + final Lesson lesson = CCUtils.createAdditionalLesson(course, project); + if (lesson != null) { final int sectionId = postModule(id, 2, EduNames.PYCHARM_ADDITIONAL); - final Lesson lesson = new Lesson(); - lesson.setName(EduNames.PYCHARM_ADDITIONAL); - final Task task = new Task(); - task.setLesson(lesson); - task.setName(EduNames.PYCHARM_ADDITIONAL); - task.setIndex(1); - for (VirtualFile file : files) { - try { - if (file != null) { - if (EduUtils.isImage(file.getName())) { - task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()))); - } - else { - task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream())); - } - } - } - catch (IOException e) { - LOG.error("Can't find file " + file.getPath()); - } - } - lesson.addTask(task); - lesson.setIndex(1); - final int lessonId = postLesson(project, lesson, indicator); + final int lessonId = postLesson(project, lesson); postUnit(lessonId, 1, sectionId); } } @@ -255,7 +223,7 @@ public class CCStepicConnector { return -1; } - public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { + public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson) { final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId())); String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); @@ -279,7 +247,10 @@ public class CCStepicConnector { } for (Task task : lesson.getTaskList()) { - indicator.checkCanceled(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + } postTask(project, task, lesson.getId()); } return lesson.getId(); @@ -290,7 +261,7 @@ public class CCStepicConnector { return -1; } - public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { + public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson) { final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons"); String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); @@ -311,7 +282,10 @@ public class CCStepicConnector { final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0); lesson.setId(postedLesson.getId()); for (Task task : lesson.getTaskList()) { - indicator.checkCanceled(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + } postTask(project, task, postedLesson.getId()); } return postedLesson.getId(); diff --git a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java index da549bc885e8..74dd51a872c8 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java @@ -21,8 +21,6 @@ import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; - public interface EduPluginConfigurator { String EP_NAME = "Edu.pluginConfigurator"; LanguageExtension INSTANCE = new LanguageExtension<>(EP_NAME); @@ -64,7 +62,7 @@ public interface EduPluginConfigurator { /** * Used in educator plugin to filter files to be packed into course archive */ - boolean excludeFromArchive(@NotNull File pathname); + boolean excludeFromArchive(@NotNull String name); /** * @return true for all the test files including tests for subtasks @@ -128,4 +126,7 @@ public interface EduPluginConfigurator { @Nullable String moduleDir) { } + default String getBundledCoursePath() { + return null; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java index 8173ff07d68c..335857dfc4a0 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java @@ -39,6 +39,7 @@ import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener; import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; @@ -49,14 +50,12 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.HyperlinkEvent; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import static com.jetbrains.edu.learning.StudyUtils.execCancelable; -import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse; public class StudyProjectComponent implements ProjectComponent { @@ -150,15 +149,8 @@ public class StudyProjectComponent implements ProjectComponent { final CourseInfo info = CourseInfo.fromCourse(currentCourse); if (info == null) return; - final File resourceDirectory = new File(currentCourse.getCourseDirectory()); - if (resourceDirectory.exists()) { - FileUtil.delete(resourceDirectory); - } - final Course course = EduStepicConnector.getCourse(myProject, info); - if (course == null) return; - flushCourse(course); course.initCourse(false); EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); @@ -175,14 +167,14 @@ public class StudyProjectComponent implements ProjectComponent { Lesson studentLesson = currentCourse.getLesson(lesson.getId()); final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex); - final File lessonDir = new File(myProject.getBasePath(), lessonDirName); - if (!lessonDir.exists()){ - final File fromLesson = new File(resourceDirectory, lessonDirName); + final VirtualFile baseDir = myProject.getBaseDir(); + final VirtualFile lessonDir = baseDir.findChild(lessonDirName); + if (lessonDir == null) { try { - FileUtil.copyDir(fromLesson, lessonDir); + StudyGenerator.createLesson(lesson, baseDir); } catch (IOException e) { - LOG.warn("Failed to copy lesson " + fromLesson.getPath()); + LOG.error("Failed to create lesson"); } lesson.setIndex(lessonIndex); lesson.initLesson(currentCourse, false); @@ -210,15 +202,14 @@ public class StudyProjectComponent implements ProjectComponent { task.setIndex(index); final String taskDirName = EduNames.TASK + String.valueOf(index); - final File toTask = new File(lessonDir, taskDirName); + final VirtualFile taskDir = lessonDir.findChild(taskDirName); - final String taskPath = FileUtil.join(resourceDirectory.getPath(), lessonDirName, taskDirName); - final File taskDir = new File(taskPath); - if (!taskDir.exists()) return; - final File[] taskFiles = taskDir.listFiles(); - if (taskFiles == null) continue; - for (File fromFile : taskFiles) { - copyFile(fromFile, new File(toTask, fromFile.getName())); + if (taskDir != null) return; + try { + StudyGenerator.createTask(task, lessonDir); + } + catch (IOException e) { + LOG.error("Failed to create task"); } tasks.add(task); } @@ -231,17 +222,6 @@ public class StudyProjectComponent implements ProjectComponent { notification.notify(myProject); } - private static void copyFile(@NotNull final File from, @NotNull final File to) { - if (from.exists()) { - try { - FileUtil.copyFileOrDir(from, to); - } - catch (IOException e) { - LOG.warn("Failed to copy " + from.getName()); - } - } - } - private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) { KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx(); for (Keymap keymap : keymapManager.getAllKeymaps()) { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java index e6b1c052afb2..1a8165ebcc16 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java @@ -528,9 +528,9 @@ public class StudySerializationUtils { stepOptionsJson = convertToSecondVersion(stepOptionsJson); case 2: stepOptionsJson = convertToThirdVersion(stepOptionsJson); - // uncomment for future versions - //case 3: - // stepOptionsJson = convertToFourthVersion(stepOptionsJson); + // uncomment for future versions + //case 3: + // stepOptionsJson = convertToFourthVersion(stepOptionsJson); } convertSubtaskInfosToMap(stepOptionsJson); StepicWrappers.StepOptions stepOptions = diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java index 9d74279928a7..8b981f97cc74 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -13,14 +13,12 @@ import com.intellij.util.xmlb.XmlSerializer; import com.intellij.util.xmlb.annotations.Transient; import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.ui.StudyToolWindow; import org.jdom.Element; import org.jdom.output.XMLOutputter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,7 +37,6 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb public int VERSION = 5; public Map> myUserTests = new HashMap<>(); - public List myInvisibleFiles = new ArrayList<>(); public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx(); private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT; @@ -150,12 +147,6 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb VERSION = CURRENT_VERSION; if (myCourse != null) { myCourse.initCourse(true); - if (version != VERSION) { - final File updatedCourse = new File(StudyProjectGenerator.OUR_COURSES_DIR, myCourse.getName()); - if (updatedCourse.exists()) { - myCourse.setCourseDirectory(updatedCourse.getAbsolutePath()); - } - } } } catch (StudySerializationUtils.StudyUnrecognizedFormatException e) { @@ -167,14 +158,6 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb return ServiceManager.getService(project, StudyTaskManager.class); } - public void addInvisibleFiles(String filePath) { - myInvisibleFiles.add(filePath); - } - - public boolean isInvisibleFile(String path) { - return myInvisibleFiles.contains(path); - } - public boolean shouldUseJavaFx() { return myShouldUseJavaFx; } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java index 94651eecc62a..8909c6936fd7 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java @@ -7,6 +7,7 @@ import com.intellij.ide.IdeView; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.util.EditorHelper; import com.intellij.lang.Language; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -40,8 +41,9 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; @@ -49,6 +51,7 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.content.Content; @@ -72,9 +75,7 @@ import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.stepic.StepicUser; import com.jetbrains.edu.learning.ui.StudyToolWindow; import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; import com.petebevin.markdown.MarkdownProcessor; @@ -84,9 +85,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.io.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; +import java.util.*; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -238,20 +237,6 @@ public class StudyUtils { } } - public static File copyResourceFile(@NotNull final String sourceName, @NotNull final String copyName, @NotNull final Project project, - @NotNull final Task task) - throws IOException { - final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - final Course course = taskManager.getCourse(); - int taskNum = task.getIndex(); - int lessonNum = task.getLesson().getIndex(); - assert course != null; - final String pathToResource = FileUtil.join(course.getCourseDirectory(), EduNames.LESSON + lessonNum, EduNames.TASK + taskNum); - final File resourceFile = new File(pathToResource, copyName); - FileUtil.copy(new File(pathToResource, sourceName), resourceFile); - return resourceFile; - } - @Nullable public static Sdk findSdk(@NotNull final Task task, @NotNull final Project project) { final Language language = task.getLesson().getCourse().getLanguageById(); @@ -413,34 +398,6 @@ public class StudyUtils { } } - - @Nullable - public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) { - Task task = taskFile.getTask(); - String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex()); - String taskDir = EduNames.TASK + String.valueOf(task.getIndex()); - Course course = task.getLesson().getCourse(); - File resourceFile = new File(course.getCourseDirectory()); - if (!resourceFile.exists()) { - return null; - } - String patternPath = FileUtil.join(resourceFile.getPath(), lessonDir, taskDir, name); - VirtualFile patternFile = VfsUtil.findFileByIoFile(new File(patternPath), true); - if (patternFile == null) { - return null; - } - return patternFile; - } - - @Nullable - public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) { - VirtualFile patternFile = getPatternFile(taskFile, name); - if (patternFile == null) { - return null; - } - return FileDocumentManager.getInstance().getDocument(patternFile); - } - public static boolean isRenameableOrMoveable(@NotNull final Project project, @NotNull final Course course, @NotNull final PsiElement element) { if (element instanceof PsiFile) { VirtualFile virtualFile = ((PsiFile)element).getVirtualFile(); @@ -623,23 +580,6 @@ public class StudyUtils { return course != null && EduNames.STUDY.equals(course.getCourseMode()); } - @NotNull - public static File getCourseDirectory(Course course) { - final File courseDirectory; - if (course.isAdaptive()) { - StepicUser user = StudySettings.getInstance().getUser(); - String email = user == null ? "" : user.getEmail(); - - courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, - StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName() - + "_" + email); - } - else { - courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName()); - } - return courseDirectory; - } - public static boolean hasJavaFx() { try { Class.forName("javafx.application.Platform"); @@ -880,4 +820,48 @@ public class StudyUtils { LOG.error(e); } } + public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) { + LocalFileSystem.getInstance().refresh(false); + final Lesson firstLesson = getFirst(course.getLessons()); + if (firstLesson == null) return; + final Task firstTask = getFirst(firstLesson.getTaskList()); + if (firstTask == null) return; + final VirtualFile taskDir = firstTask.getTaskDir(project); + if (taskDir == null) return; + final Map taskFiles = firstTask.getTaskFiles(); + VirtualFile activeVirtualFile = null; + for (Map.Entry entry : taskFiles.entrySet()) { + final String relativePath = entry.getKey(); + final TaskFile taskFile = entry.getValue(); + taskDir.refresh(false, true); + final VirtualFile virtualFile = taskDir.findFileByRelativePath(relativePath); + if (virtualFile != null) { + if (!taskFile.getActivePlaceholders().isEmpty()) { + activeVirtualFile = virtualFile; + } + } + } + if (activeVirtualFile != null) { + final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile); + ProjectView.getInstance(project).select(file, activeVirtualFile, false); + final FileEditor[] editors = FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); + if (editors.length == 0) { + return; + } + final FileEditor studyEditor = editors[0]; + if (studyEditor instanceof StudyEditor) { + selectFirstAnswerPlaceholder((StudyEditor)studyEditor, project); + } + FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); + } + else { + String first = getFirst(taskFiles.keySet()); + if (first != null) { + NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first); + if (firstFile != null) { + FileEditorManager.getInstance(project).openFile(firstFile, true); + } + } + } + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java index 85f3d3869ae3..8c6ea0bd36cf 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java @@ -3,13 +3,9 @@ package com.jetbrains.edu.learning.actions; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudySubtaskUtils; import com.jetbrains.edu.learning.StudyTaskManager; @@ -17,8 +13,8 @@ import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.Nullable; @@ -41,24 +37,12 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { return; } StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - final StudyState studyState = new StudyState(studyEditor); - if (answerPlaceholder.getTaskFile().getTask() instanceof TaskWithSubtasks) { - StudySubtaskUtils.refreshPlaceholder(studyState.getEditor(), answerPlaceholder); - return; + if (studyEditor != null) { + StudySubtaskUtils.refreshPlaceholder(studyEditor.getEditor(), answerPlaceholder); + final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + answerPlaceholder.reset(); + taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked); } - Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); - if (patternDocument == null) { - return; - } - AnswerPlaceholder.MyInitialState initialState = answerPlaceholder.getInitialState(); - int startOffset = initialState.getOffset(); - final String text = patternDocument.getText(new TextRange(startOffset, startOffset + initialState.getLength())); - CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { - Document document = studyState.getEditor().getDocument(); - int offset = answerPlaceholder.getOffset(); - document.deleteString(offset, offset + answerPlaceholder.getRealLength()); - document.insertString(offset, text); - }), NAME, null); } @Override diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java index 08fb50fcfb43..5cbb7c749ecd 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java @@ -76,7 +76,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { } } else { - if (!resetTaskFile(editor.getDocument(), project, taskFile, studyState.getVirtualFile().getName())) { + if (!resetTaskFile(editor.getDocument(), project, taskFile)) { Messages.showInfoMessage("The initial text of task file is unavailable", "Failed to Refresh Task File"); return; } @@ -95,16 +95,13 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { () -> IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true)); StudyNavigator.navigateToFirstAnswerPlaceholder(editor, taskFile); - showBalloon(project, "You can start again now", MessageType.INFO); + showBalloon(project, MessageType.INFO); } private static boolean resetTaskFile(@NotNull final Document document, @NotNull final Project project, - TaskFile taskFile, - String name) { - if (!resetDocument(document, taskFile, name)) { - return false; - } + TaskFile taskFile) { + resetDocument(document, taskFile); final Task task = taskFile.getTask(); task.setStatus(StudyStatus.Unchecked); if (task instanceof ChoiceTask) { @@ -116,9 +113,9 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { return true; } - private static void showBalloon(@NotNull final Project project, String text, @NotNull final MessageType messageType) { + private static void showBalloon(@NotNull final Project project, @NotNull final MessageType messageType) { BalloonBuilder balloonBuilder = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, messageType, null); + JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can start again now", messageType, null); final Balloon balloon = balloonBuilder.createBalloon(); StudyEditor selectedStudyEditor = StudyUtils.getSelectedStudyEditor(project); assert selectedStudyEditor != null; @@ -135,19 +132,14 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { } - private static boolean resetDocument(@NotNull final Document document, - @NotNull final TaskFile taskFile, - String fileName) { - final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName); - if (patternDocument == null) { - return false; - } + private static void resetDocument(@NotNull final Document document, + @NotNull final TaskFile taskFile) { StudyUtils.deleteGuardedBlocks(document); taskFile.setTrackChanges(false); clearDocument(document); - document.setText(patternDocument.getCharsSequence()); + + document.setText(taskFile.text); taskFile.setTrackChanges(true); - return true; } private static void clearDocument(@NotNull final Document document) { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java index 3604bd8665c0..daa1d3491eb5 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java @@ -21,7 +21,6 @@ import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; public class StudySmartChecker { @@ -40,7 +39,6 @@ public class StudySmartChecker { @NotNull final VirtualFile virtualFile, @NotNull final Document usersDocument) { VirtualFile fileWindows = null; - File resourceFile = null; VirtualFile windowCopy = null; try { final int index = placeholder.getIndex(); @@ -49,7 +47,6 @@ public class StudySmartChecker { final FileDocumentManager documentManager = FileDocumentManager.getInstance(); final Document windowDocument = documentManager.getDocument(windowCopy); if (windowDocument != null) { - resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask()); TaskFile windowTaskFile = answerTaskFile.getTask().copy().getTaskFile(StudyUtils.pathRelativeToTask(virtualFile)); if (windowTaskFile == null) { return; @@ -81,9 +78,6 @@ public class StudySmartChecker { finally { StudyUtils.deleteFile(windowCopy); StudyUtils.deleteFile(fileWindows); - if (resourceFile != null && resourceFile.exists() && !resourceFile.delete()) { - LOG.error("failed to delete", resourceFile.getPath()); - } } } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java index 0f6a8e5fe719..0ec946b2aefe 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java @@ -13,18 +13,20 @@ import com.intellij.openapi.command.undo.UndoableAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.PsiDirectory; +import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; +import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -120,68 +122,52 @@ public class EduUtils { VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } - - public static VirtualFile copyFile(Object requestor, VirtualFile toDir, VirtualFile file) { - Document document = FileDocumentManager.getInstance().getDocument(file); - if (document != null) { - FileDocumentManager.getInstance().saveDocument(document); - } - String taskRelativePath = StudyUtils.pathRelativeToTask(file); - try { - VirtualFile userFile = toDir.findFileByRelativePath(taskRelativePath); - if (userFile != null) { - userFile.delete(requestor); - } - return VfsUtil.copyFileRelative(requestor, file, toDir, taskRelativePath); - } - catch (IOException e) { - LOG.info("Failed to create file " + taskRelativePath + " in folder " + toDir.getPath(), e); - } - return null; - } - @Nullable - public static Pair createStudentFile(Object requestor, - Project project, - VirtualFile answerFile, - VirtualFile parentDir, - @Nullable Task task, - int targetSubtaskIndex) { - - VirtualFile studentFile = copyFile(requestor, parentDir, answerFile); - if (studentFile == null) { - return null; - } - Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile); - if (studentDocument == null) { - return null; - } - if (task == null) { - task = StudyUtils.getTaskForFile(project, answerFile); + public static TaskFile createStudentFile(Project project, VirtualFile answerFile, @Nullable Task task, int targetSubtaskIndex) { + try { if (task == null) { + task = StudyUtils.getTaskForFile(project, answerFile); + if (task == null) { + return null; + } + task = task.copy(); + } + TaskFile taskFile = task.getTaskFile(StudyUtils.pathRelativeToTask(answerFile)); + if (taskFile == null) { return null; } - task = task.copy(); - } - TaskFile taskFile = task.getTaskFile(StudyUtils.pathRelativeToTask(answerFile)); - if (taskFile == null) { - return null; - } - EduDocumentListener listener = new EduDocumentListener(taskFile, false); - studentDocument.addDocumentListener(listener); - taskFile.setTrackLengths(false); - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - if (task instanceof TaskWithSubtasks) { - int fromSubtask = ((TaskWithSubtasks)task).getActiveSubtaskIndex(); - placeholder.switchSubtask(studentDocument, fromSubtask, targetSubtaskIndex); + if (isImage(taskFile.name)) { + taskFile.text = Base64.encodeBase64String(answerFile.contentsToByteArray()); + return taskFile; } + final LightVirtualFile studentFile = new LightVirtualFile("student_task", PlainTextFileType.INSTANCE, + VfsUtilCore.loadText(answerFile)); + Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile); + if (studentDocument == null) { + return null; + } + EduDocumentListener listener = new EduDocumentListener(taskFile, false); + studentDocument.addDocumentListener(listener); + taskFile.setTrackLengths(false); + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + if (task instanceof TaskWithSubtasks) { + int fromSubtask = ((TaskWithSubtasks)task).getActiveSubtaskIndex(); + placeholder.switchSubtask(studentDocument, fromSubtask, targetSubtaskIndex); + } + } + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + replaceWithTaskText(studentDocument, placeholder, targetSubtaskIndex); + } + taskFile.setTrackChanges(true); + studentDocument.removeDocumentListener(listener); + taskFile.text = studentDocument.getImmutableCharSequence().toString(); + return taskFile; } - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - replaceWithTaskText(studentDocument, placeholder, targetSubtaskIndex); + catch (IOException e) { + LOG.error("Failed to convert answer file to student one"); } - taskFile.setTrackChanges(true); - studentDocument.removeDocumentListener(listener); - return Pair.create(studentFile, taskFile); + + return null; } private static void replaceWithTaskText(Document studentDocument, AnswerPlaceholder placeholder, int toSubtaskIndex) { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java index 392ce6b4c6b2..44976a6078dc 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java @@ -18,13 +18,13 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; public class Course { @Expose private List lessons = new ArrayList<>(); @Expose private List authors = new ArrayList<>(); @Expose private String description; @Expose private String name; - private String myCourseDirectory = ""; @Expose private int id; @Expose @SerializedName("update_date") private Date myUpdateDate; @Expose private boolean isAdaptive = false; @@ -35,8 +35,7 @@ public class Course { private String courseType = EduNames.PYCHARM; private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes - public Course() { - } + public Course() {} /** * Initializes state of course @@ -48,12 +47,18 @@ public class Course { } public List getLessons() { - return lessons; + return getLessons(false); + } + + public List getLessons(boolean withAadditional) { + return withAadditional ? lessons + : lessons.stream().filter(lesson -> !EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())).collect(Collectors.toList()); } public void setLessons(List lessons) { this.lessons = lessons; } + public void addLessons(List lessons) { this.lessons.addAll(lessons); } @@ -62,6 +67,10 @@ public class Course { lessons.add(lesson); } + public void removeLesson(Lesson lesson) { + lessons.remove(lesson); + } + public Lesson getLesson(@NotNull final String name) { int lessonIndex = EduUtils.getIndex(name, EduNames.LESSON); List lessons = getLessons(); @@ -118,14 +127,6 @@ public class Course { this.name = name; } - public String getCourseDirectory() { - return myCourseDirectory; - } - - public void setCourseDirectory(@NotNull final String courseDirectory) { - myCourseDirectory = courseDirectory; - } - public String getDescription() { return description; } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/CourseInfo.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/CourseInfo.java index acac3c5f951d..89fe65b8941e 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/CourseInfo.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/CourseInfo.java @@ -1,7 +1,6 @@ package com.jetbrains.edu.learning.courseFormat; import com.google.gson.annotations.SerializedName; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import com.jetbrains.edu.learning.stepic.StepicUser; import org.jetbrains.annotations.NotNull; @@ -10,7 +9,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.stream.Collectors; /** * Implementation of class which contains information to be shawn in course description in tool window @@ -152,10 +150,11 @@ public class CourseInfo { public static CourseInfo fromCourse(@Nullable final Course course) { if (course == null) return null; - final List infos = StudyProjectGenerator.getCoursesFromCache().stream(). - filter(info -> info.id == course.getId()).collect(Collectors.toList()); - if (infos.isEmpty()) return null; - return infos.get(0); + CourseInfo courseInfo = new CourseInfo(); + courseInfo.setName(course.getName()); + courseInfo.setDescription(course.getDescription()); + courseInfo.setAuthors(course.getAuthors()); + return courseInfo; } public List getSections() { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java index d5b56e06dd01..f5467dddee04 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java @@ -21,6 +21,11 @@ import java.util.Map; /** * Implementation of task which contains task files, tests, input file for tests + * + * To implement new task there are 3 steps to be done: + * - extend Task class + * - go to Lesson and update elementTypes in taskList AbstractCollection. Needed for proper xml serialization + * - Update TaskSerializer and TaskDeserializer in StudySerializationUtil to handle json serialization */ public class Task implements StudyItem { @Expose private String name; @@ -35,8 +40,10 @@ public class Task implements StudyItem { @SerializedName("task_files") @Expose public Map taskFiles = new HashMap<>(); - protected Map testsText = new HashMap<>(); - protected Map taskTexts = new HashMap<>(); + @SerializedName("test_files") + @Expose protected Map testsText = new HashMap<>(); + @SerializedName("task_texts") + @Expose protected Map taskTexts = new HashMap<>(); @Transient private Lesson myLesson; @Expose @SerializedName("update_date") private Date myUpdateDate; diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java index e19a67d25430..91e5eb7523c8 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java @@ -1,125 +1,33 @@ package com.jetbrains.edu.learning.courseGeneration; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; public class StudyGenerator { - private StudyGenerator() { - - } private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName()); - /** - * Creates task files in its task folder in project user created - * - * @param taskDir project directory of task which task file belongs to - * @param resourceRoot directory where original task file stored - * @throws IOException - */ - public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot, - @NotNull final String name) throws IOException { - String systemIndependentName = FileUtil.toSystemIndependentName(name); - File resourceFile = new File(resourceRoot, name); - File fileInProject = new File(taskDir.getPath(), systemIndependentName); - FileUtil.copyFileOrDir(resourceFile, fileInProject); - } - - /** - * Creates task directory in its lesson folder in project user created - * - * @param lessonDir project directory of lesson which task belongs to - * @param resourceRoot directory where original task file stored - * @throws IOException - */ - public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot, - @NotNull final Project project) throws IOException { - VirtualFile taskDir = lessonDir.createChildDirectory(project, EduNames.TASK + Integer.toString(task.getIndex())); - File newResourceRoot = new File(resourceRoot, taskDir.getName()); - int i = 0; - for (Map.Entry taskFile : task.getTaskFiles().entrySet()) { - TaskFile taskFileContent = taskFile.getValue(); - taskFileContent.setIndex(i); - i++; - createTaskFile(taskDir, newResourceRoot, taskFile.getKey()); - } - File[] filesInTask = newResourceRoot.listFiles(); - if (filesInTask != null) { - for (File file : filesInTask) { - String fileName = file.getName(); - if (!task.isTaskFile(fileName)) { - File resourceFile = new File(newResourceRoot, fileName); - File fileInProject = new File(taskDir.getCanonicalPath(), fileName); - FileUtil.copyFileOrDir(resourceFile, fileInProject); - if (!StudyUtils.isTestsFile(project, fileName) && !StudyUtils.isTaskDescriptionFile(fileName)) { - StudyTaskManager.getInstance(project).addInvisibleFiles(FileUtil.toSystemIndependentName(fileInProject.getPath())); - } - } - } - } - } - - /** - * Creates lesson directory in its course folder in project user created - * - * @param courseDir project directory of course - * @param resourceRoot directory where original lesson stored - * @throws IOException - */ - public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir, @NotNull final File resourceRoot, - @NotNull final Project project) throws IOException { - if (EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) return; - String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex()); - VirtualFile lessonDir = courseDir.createChildDirectory(project, lessonDirName); - final List taskList = lesson.getTaskList(); - for (int i = 1; i <= taskList.size(); i++) { - Task task = taskList.get(i - 1); - task.setIndex(i); - createTask(task, lessonDir, new File(resourceRoot, lessonDir.getName()), project); - } - } - - /** - * Creates course directory in project user created - * - * @param baseDir project directory - * @param resourceRoot directory where original course is stored - */ - public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot, - @NotNull final Project project) { + private StudyGenerator() {} + public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir) { try { - final List lessons = course.getLessons(); + final List lessons = course.getLessons(true); for (int i = 1; i <= lessons.size(); i++) { Lesson lesson = lessons.get(i - 1); lesson.setIndex(i); - createLesson(lesson, baseDir, resourceRoot, project); - } - File[] files = resourceRoot.listFiles( - (dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS)); - if (files != null) { - for (File file : files) { - File dir = new File(baseDir.getPath(), file.getName()); - if (file.isDirectory()) { - FileUtil.copyDir(file, dir); - continue; - } - FileUtil.copy(file, dir); - } + createLesson(lesson, baseDir); } } catch (IOException e) { @@ -127,4 +35,88 @@ public class StudyGenerator { } } + public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir) throws IOException { + if (EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) { + createAdditionalFiles(lesson, courseDir); + } + else { + String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex()); + VirtualFile lessonDir = courseDir.createChildDirectory(courseDir, lessonDirName); + final List taskList = lesson.getTaskList(); + for (int i = 1; i <= taskList.size(); i++) { + Task task = taskList.get(i - 1); + task.setIndex(i); + createTask(task, lessonDir); + } + } + } + + public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir) throws IOException { + VirtualFile taskDir = lessonDir.createChildDirectory(lessonDir, EduNames.TASK + Integer.toString(task.getIndex())); + int i = 0; + for (Map.Entry taskFile : task.getTaskFiles().entrySet()) { + TaskFile taskFileContent = taskFile.getValue(); + taskFileContent.setIndex(i); + i++; + createTaskFile(taskDir, taskFile.getValue()); + } + createTestFiles(taskDir, task); + createDescriptions(taskDir, task); + } + + public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final TaskFile taskFile) throws IOException { + final String name = taskFile.name; + createChildFile(taskDir, name, taskFile.text); + } + + private static void createDescriptions(VirtualFile taskDir, Task task) throws IOException { + final Map texts = task.getTaskTexts(); + for (Map.Entry entry : texts.entrySet()) { + final String name = entry.getKey(); + final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name); + VfsUtil.saveText(virtualTaskFile, entry.getValue()); + } + } + + private static void createTestFiles(VirtualFile taskDir, Task task) throws IOException { + final Map tests = task.getTestsText(); + for (Map.Entry entry : tests.entrySet()) { + final String name = entry.getKey(); + final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name); + VfsUtil.saveText(virtualTaskFile, entry.getValue()); + } + } + + private static void createAdditionalFiles(Lesson lesson, VirtualFile courseDir) throws IOException { + final List taskList = lesson.getTaskList(); + if (taskList.size() != 1) return; + final Task task = taskList.get(0); + for (Map.Entry entry : task.getTestsText().entrySet()) { + createChildFile(courseDir, entry.getKey(), entry.getValue()); + } + } + + + private static void createChildFile(@NotNull VirtualFile taskDir, String name, String text) throws IOException { + String newDirectories = null; + String fileName = name; + VirtualFile dir = taskDir; + if (name.contains("/")) { + int pos = name.lastIndexOf("/"); + fileName = name.substring(pos + 1); + newDirectories = name.substring(0, pos); + } + if (newDirectories != null) { + dir = VfsUtil.createDirectoryIfMissing(taskDir, newDirectories); + } + if (dir != null) { + final VirtualFile virtualTaskFile = dir.createChildData(taskDir, fileName); + if (EduUtils.isImage(name)) { + virtualTaskFile.setBinaryContent(Base64.decodeBase64(text)); + } + else { + VfsUtil.saveText(virtualTaskFile, text); + } + } + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 84f63d113e7a..3bd94efe7e30 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -1,62 +1,48 @@ package com.jetbrains.edu.learning.courseGeneration; -import com.google.gson.*; -import com.google.gson.stream.JsonReader; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.intellij.facet.ui.ValidationResult; -import com.intellij.ide.projectView.ProjectView; +import com.intellij.lang.LanguageExtensionPoint; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; -import com.intellij.platform.templates.github.ZipUtil; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.io.zip.JBZipEntry; +import com.intellij.util.io.zip.JBZipFile; +import com.jetbrains.edu.learning.EduPluginConfigurator; import com.jetbrains.edu.learning.StudySerializationUtils; import com.jetbrains.edu.learning.StudySettings; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.CourseInfo; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import com.jetbrains.edu.learning.stepic.StepicUser; -import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.*; -import java.util.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import static com.jetbrains.edu.learning.StudyUtils.execCancelable; public class StudyProjectGenerator { - public static final String AUTHOR_ATTRIBUTE = "authors"; - public static final String LANGUAGE_ATTRIBUTE = "language"; - public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__"; - public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses"); private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName()); - private static final String COURSE_NAME_ATTRIBUTE = "name"; - private static final String COURSE_DESCRIPTION = "description"; - private static final String CACHE_NAME = "courseNames.txt"; private final List myListeners = ContainerUtil.newArrayList(); private List myCourses = new ArrayList<>(); + private List myLocalCourses = new ArrayList<>(); private List myEnrolledCoursesIds = new ArrayList<>(); protected CourseInfo mySelectedCourseInfo; @@ -97,39 +83,24 @@ public class StudyProjectGenerator { Messages.showWarningDialog("There is no recommended tasks for this adaptive course", "Error in Course Creation"); return; } - final File courseDirectory = StudyUtils.getCourseDirectory(course); StudyTaskManager.getInstance(project).setCourse(course); ApplicationManager.getApplication().runWriteAction(() -> { - StudyGenerator.createCourse(course, baseDir, courseDirectory, project); - course.setCourseDirectory(courseDirectory.getAbsolutePath()); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + StudyGenerator.createCourse(course, baseDir); StudyUtils.registerStudyToolWindow(course, project); - openFirstTask(course, project); + StudyUtils.openFirstTask(course, project); EduUsagesCollector.projectTypeCreated(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY); }); } @Nullable public Course getCourse(@NotNull final Project project) { - - final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); - if (courseFile.exists()) { - final Course course = readCourseFromCache(courseFile, false); - if (course != null && course.isUpToDate()) { + final CourseInfo info = mySelectedCourseInfo; + for (Course course : myLocalCourses) { + if (course.getName().equals(info.getName())){ + course.initCourse(false); return course; } - return getCourseFromStepic(project); - } - else { - final StepicUser user = StudySettings.getInstance().getUser(); - if (user != null) { - final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX + - mySelectedCourseInfo.getName() + "_" + - user.getEmail()), EduNames.COURSE_META_FILE); - if (adaptiveCourseFile.exists()) { - return readCourseFromCache(adaptiveCourseFile, true); - } - } + } return getCourseFromStepic(project); } @@ -140,7 +111,6 @@ public class StudyProjectGenerator { return execCancelable(() -> { final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo); if (StudyUtils.isCourseValid(course)) { - flushCourse(course); course.initCourse(false); } return course; @@ -148,267 +118,12 @@ public class StudyProjectGenerator { }, "Creating Course", true, project); } - @Nullable - private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) { - Reader reader = null; - try { - reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); - Gson gson = - new GsonBuilder().registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseFile)) - .registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskDeserializer()).create(); - final Course course = gson.fromJson(reader, Course.class); - course.initCourse(isAdaptive); - return course; - } - catch (UnsupportedEncodingException | FileNotFoundException e) { - LOG.warn(e.getMessage()); - } - finally { - StudyUtils.closeSilently(reader); - } - return null; - } - - public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) { - LocalFileSystem.getInstance().refresh(false); - final Lesson firstLesson = StudyUtils.getFirst(course.getLessons()); - if (firstLesson == null) return; - final Task firstTask = StudyUtils.getFirst(firstLesson.getTaskList()); - if (firstTask == null) return; - final VirtualFile taskDir = firstTask.getTaskDir(project); - if (taskDir == null) return; - final Map taskFiles = firstTask.getTaskFiles(); - VirtualFile activeVirtualFile = null; - for (Map.Entry entry : taskFiles.entrySet()) { - final String relativePath = entry.getKey(); - final TaskFile taskFile = entry.getValue(); - taskDir.refresh(false, true); - final VirtualFile virtualFile = taskDir.findFileByRelativePath(relativePath); - if (virtualFile != null) { - if (!taskFile.getActivePlaceholders().isEmpty()) { - activeVirtualFile = virtualFile; - } - } - } - if (activeVirtualFile != null) { - final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile); - ProjectView.getInstance(project).select(file, activeVirtualFile, false); - final FileEditor[] editors = FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); - if (editors.length == 0) { - return; - } - final FileEditor studyEditor = editors[0]; - if (studyEditor instanceof StudyEditor) { - StudyUtils.selectFirstAnswerPlaceholder((StudyEditor)studyEditor, project); - } - FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); - } - else { - String first = StudyUtils.getFirst(taskFiles.keySet()); - if (first != null) { - NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first); - if (firstFile != null) { - FileEditorManager.getInstance(project).openFile(firstFile, true); - } - } - } - } - - public static void flushCourse(@NotNull final Course course) { - final File courseDirectory = StudyUtils.getCourseDirectory(course); - FileUtil.createDirectory(courseDirectory); - flushCourseJson(course, courseDirectory); - - int lessonIndex = 1; - List additionalLessons = new ArrayList<>(); - for (Lesson lesson : course.getLessons()) { - if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) { - flushAdditionalFiles(courseDirectory, lesson); - additionalLessons.add(lesson); - } - else { - final File lessonDirectory = new File(courseDirectory, EduNames.LESSON + String.valueOf(lessonIndex)); - flushLesson(lessonDirectory, lesson); - lessonIndex += 1; - } - } - for (Lesson lesson : additionalLessons) { - course.getLessons().remove(lesson); - } - } - - private static void flushAdditionalFiles(File courseDirectory, Lesson lesson) { - final List taskList = lesson.getTaskList(); - if (taskList.size() != 1) return; - final Task task = taskList.get(0); - for (Map.Entry entry : task.getTestsText().entrySet()) { - final String name = entry.getKey(); - final String text = entry.getValue(); - final File file = new File(courseDirectory, name); - FileUtil.createIfDoesntExist(file); - try { - if (EduUtils.isImage(name)) { - FileUtil.writeToFile(file, Base64.decodeBase64(text)); - } - else { - FileUtil.writeToFile(file, text); - } - } - catch (IOException e) { - LOG.error("ERROR copying file " + name); - } - } - } - - public static void flushLesson(@NotNull final File lessonDirectory, @NotNull final Lesson lesson) { - FileUtil.createDirectory(lessonDirectory); - int taskIndex = 1; - for (Task task : lesson.taskList) { - final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(taskIndex)); - flushTask(task, taskDirectory); - taskIndex += 1; - } - } - - public static void flushTask(@NotNull final Task task, @NotNull final File taskDirectory) { - FileUtil.createDirectory(taskDirectory); - for (Map.Entry taskFileEntry : task.taskFiles.entrySet()) { - final String name = FileUtil.toSystemDependentName(taskFileEntry.getKey()); - final TaskFile taskFile = taskFileEntry.getValue(); - final File file = new File(taskDirectory, name); - FileUtil.createIfDoesntExist(file); - - try { - if (EduUtils.isImage(taskFile.name)) { - FileUtil.writeToFile(file, Base64.decodeBase64(taskFile.text)); - } - else { - FileUtil.writeToFile(file, taskFile.text); - } - } - catch (IOException e) { - LOG.error(e); - } - } - createFiles(taskDirectory, task.getTestsText()); - createFiles(taskDirectory, task.getTaskTexts()); - } - - public static void createFiles(@NotNull File taskDirectory, Map files) { - for (Map.Entry entry : files.entrySet()) { - final File file = new File(taskDirectory, entry.getKey()); - if (file.exists()) { - FileUtil.delete(file); - } - FileUtil.createIfDoesntExist(file); - try { - FileUtil.writeToFile(file, entry.getValue()); - } - catch (IOException e) { - LOG.error(e); - } - } - } - - public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) { - final Gson gson = new GsonBuilder().setPrettyPrinting(). - registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskSerializer()). - excludeFieldsWithoutExposeAnnotation().create(); - final String json = gson.toJson(course); - final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE); - final FileOutputStream fileOutputStream; - try { - fileOutputStream = new FileOutputStream(courseJson); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8"); - try { - outputStreamWriter.write(json); - } - catch (IOException e) { - Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json"); - LOG.info(e); - } - finally { - try { - outputStreamWriter.close(); - } - catch (IOException e) { - LOG.info(e); - } - } - } - catch (FileNotFoundException | UnsupportedEncodingException e) { - LOG.info(e); - } - } - - /** - * Writes courses to cache file {@link StudyProjectGenerator#CACHE_NAME} - */ - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - public static void flushCache(List courses) { - flushCache(courses, true); - } - - public static void flushCache(List courses, boolean preserveOld) { - File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME); - PrintWriter writer = null; - try { - if (!createCacheFile(cacheFile)) return; - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - - final Set courseInfos = new HashSet<>(); - courseInfos.addAll(courses); - if (preserveOld) { - courseInfos.addAll(getCoursesFromCache()); - } - - writer = new PrintWriter(cacheFile, "UTF-8"); - try { - for (CourseInfo courseInfo : courseInfos) { - final String json = gson.toJson(courseInfo); - writer.println(json); - } - } - finally { - StudyUtils.closeSilently(writer); - } - } - catch (IOException e) { - LOG.error(e); - } - finally { - StudyUtils.closeSilently(writer); - } - } - - private static boolean createCacheFile(File cacheFile) throws IOException { - if (!OUR_COURSES_DIR.exists()) { - final boolean created = OUR_COURSES_DIR.mkdirs(); - if (!created) { - LOG.error("Cannot flush courses cache. Can't create courses directory"); - return false; - } - } - if (!cacheFile.exists()) { - final boolean created = cacheFile.createNewFile(); - if (!created) { - LOG.error("Cannot flush courses cache. Can't create " + CACHE_NAME + " file"); - return false; - } - } - return true; - } - // Supposed to be called under progress public List getCourses(boolean force) { - if (OUR_COURSES_DIR.exists() && !force) { - myCourses = getCoursesFromCache(); + if (force) { + myCourses = execCancelable(() -> EduStepicConnector.getCourses(StepicUpdateSettings.getInstance().getUser())); } - if (force || myCourses.isEmpty()) { - myCourses = execCancelable(() -> EduStepicConnector.getCourses(StudySettings.getInstance().getUser())); - flushCache(myCourses); - } - if (myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) { + if (force || myCourses == null || myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) { myCourses = Collections.singletonList(getBundledIntro()); } sortCourses(myCourses); @@ -461,78 +176,45 @@ public class StudyProjectGenerator { } } - public static CourseInfo getBundledIntro() { - final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python"); - if (introCourse.exists()) { - return getCourseInfo(introCourse); + @Nullable + public CourseInfo getBundledIntro() { + final LanguageExtensionPoint[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null); + for (LanguageExtensionPoint extension : extensions) { + final EduPluginConfigurator configurator = extension.getInstance(); + final String path = configurator.getBundledCoursePath(); + if (path != null) { + return getCourseInfo(path); + } } return null; } - public static List getCoursesFromCache() { - List courses = new ArrayList<>(); - final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME); - if (!cacheFile.exists()) { - return courses; - } - try { - final FileInputStream inputStream = new FileInputStream(cacheFile); - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); - try { - String line; - while ((line = reader.readLine()) != null) { - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class); - courses.add(courseInfo); - } - } - catch (IOException | JsonSyntaxException e) { - LOG.error(e.getMessage()); - } - finally { - StudyUtils.closeSilently(reader); - } - } - catch (UnsupportedEncodingException e) { - LOG.error(e.getMessage()); - } - finally { - StudyUtils.closeSilently(inputStream); - } - } - catch (FileNotFoundException e) { - LOG.error(e.getMessage()); - } - return courses; - } - - /** - * Adds course from zip archive to courses - * - * @return added course name or null if course is invalid - */ @Nullable public CourseInfo addLocalCourse(String zipFilePath) { - File file = new File(zipFilePath); + final CourseInfo courseInfo = getCourseInfo(zipFilePath); + if (courseInfo != null) { + myCourses.add(0, courseInfo); + } + return courseInfo; + } + + @Nullable + public CourseInfo getCourseInfo(String zipFilePath) { try { - String fileName = file.getName(); - String unzippedName = fileName.substring(0, fileName.indexOf(".")); - File courseDir = new File(OUR_COURSES_DIR, unzippedName); - ZipUtil.unzip(null, courseDir, file, null, null, true); - CourseInfo courseName = addCourse(myCourses, courseDir); - flushCache(myCourses); - if (courseName != null && !courseName.getName().equals(unzippedName)) { - //noinspection ResultOfMethodCallIgnored - File dest = new File(OUR_COURSES_DIR, courseName.getName()); - if (dest.exists()) { - FileUtil.delete(dest); - } - courseDir.renameTo(dest); - //noinspection ResultOfMethodCallIgnored - courseDir.delete(); + final JBZipFile zipFile = new JBZipFile(zipFilePath); + final JBZipEntry entry = zipFile.getEntry(EduNames.COURSE_META_FILE); + byte[] bytes = entry.getData(); + final String jsonText = new String(bytes, CharsetToolkit.UTF8_CHARSET); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskDeserializer()) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + Course course = gson.fromJson(jsonText, Course.class); + final CourseInfo courseInfo = CourseInfo.fromCourse(course); + if (courseInfo != null) { + myLocalCourses.add(course); } - return courseName; + return courseInfo; } catch (IOException e) { LOG.error("Failed to unzip course archive"); @@ -540,80 +222,4 @@ public class StudyProjectGenerator { } return null; } - - /** - * Adds course to courses specified in params - * - * @param courses - * @param courseDir must be directory containing course file - * @return added course name or null if course is invalid - */ - @Nullable - private static CourseInfo addCourse(List courses, File courseDir) { - if (courseDir.isDirectory()) { - File[] courseFiles = courseDir.listFiles((dir, name) -> name.equals(EduNames.COURSE_META_FILE)); - if (courseFiles == null || courseFiles.length != 1) { - LOG.info("User tried to add course with more than one or without course files"); - return null; - } - File courseFile = courseFiles[0]; - CourseInfo courseInfo = getCourseInfo(courseFile); - if (courseInfo != null) { - courses.add(0, courseInfo); - } - return courseInfo; - } - return null; - } - - /** - * Parses course json meta file and finds course name - * - * @return information about course or null if course file is invalid - */ - @Nullable - private static CourseInfo getCourseInfo(File courseFile) { - if (courseFile.isDirectory()) { - File[] courseFiles = courseFile.listFiles((dir, name) -> name.equals(EduNames.COURSE_META_FILE)); - if (courseFiles == null || courseFiles.length != 1) { - LOG.info("More than one or without course files"); - return null; - } - courseFile = courseFiles[0]; - } - CourseInfo courseInfo = null; - BufferedReader reader = null; - try { - if (courseFile.getName().equals(EduNames.COURSE_META_FILE)) { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(courseFile), "UTF-8")); - JsonReader r = new JsonReader(reader); - JsonParser parser = new JsonParser(); - JsonElement el = parser.parse(r); - String courseName = el.getAsJsonObject().get(COURSE_NAME_ATTRIBUTE).getAsString(); - String courseDescription = el.getAsJsonObject().get(COURSE_DESCRIPTION).getAsString(); - JsonArray courseAuthors = el.getAsJsonObject().get(AUTHOR_ATTRIBUTE).getAsJsonArray(); - String language = el.getAsJsonObject().get(LANGUAGE_ATTRIBUTE).getAsString(); - courseInfo = new CourseInfo(); - courseInfo.setName(courseName); - courseInfo.setDescription(courseDescription); - courseInfo.setType("pycharm " + language); - final ArrayList authors = new ArrayList<>(); - for (JsonElement author : courseAuthors) { - final JsonObject authorAsJsonObject = author.getAsJsonObject(); - final StepicUser stepicUser = StepicUser.createEmptyUser(); - stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString()); - stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString()); - authors.add(stepicUser); - } - courseInfo.setAuthors(authors); - } - } - catch (Exception e) { - //error will be shown in UI - } - finally { - StudyUtils.closeSilently(reader); - } - return courseInfo; - } -} +} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java index fe98b766ea7a..6cac20d04ace 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java @@ -8,8 +8,6 @@ import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import org.jetbrains.annotations.NotNull; -import java.io.File; - public class EduProjectGenerator extends StudyProjectGenerator { private static final Logger LOG = Logger.getInstance(EduProjectGenerator.class); @@ -21,6 +19,5 @@ public class EduProjectGenerator extends StudyProjectGenerator { return; } StudyTaskManager.getInstance(project).setCourse(course); - course.setCourseDirectory(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()).getAbsolutePath()); } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java index f500a1ee179e..c4db5d3caf2b 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java @@ -23,7 +23,6 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.util.containers.FactoryMap; import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -31,7 +30,6 @@ public class EduUsagesCollector extends UsagesCollector { private static final String GROUP_ID = "educational"; private final FactoryMap myUsageDescriptors = new FactoryMap() { - @Nullable @Override protected UsageDescriptor create(String key) { return new UsageDescriptor(key, 0); diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 1e159afd7aa3..5bda69ff6384 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -7,16 +7,17 @@ import com.intellij.ide.projectView.ProjectView; import com.intellij.lang.Language; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; -import com.jetbrains.edu.learning.StudySettings; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.checker.StudyExecutor; @@ -27,7 +28,6 @@ import com.jetbrains.edu.learning.courseFormat.tasks.CodeTask; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.navigation.StudyNavigator; import com.jetbrains.edu.learning.ui.StudyToolWindow; @@ -45,12 +45,14 @@ import org.apache.http.util.EntityUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.concurrent.TimeUnit; import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep; @@ -67,7 +69,6 @@ public class EduAdaptiveStepicConnector { private static final String CODE_TASK_TYPE = "code"; private static final String CHOICE_TYPE_TEXT = "choice"; private static final String TEXT_STEP_TYPE = "text"; - private static final String DEFAULT_TASK_NAME = "code.py"; @Nullable public static Task getNextRecommendation(@NotNull Project project, @NotNull Course course) { @@ -78,7 +79,7 @@ public class EduAdaptiveStepicConnector { return null; } - StepicUser user = StudySettings.getInstance().getUser(); + StepicUser user = StepicUpdateSettings.getInstance().getUser(); if (user == null) { LOG.warn("User is null"); return null; @@ -128,7 +129,16 @@ public class EduAdaptiveStepicConnector { } catch (IOException e) { LOG.warn(e.getMessage()); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Connection problems, Please, try again")); + + final String connectionMessages = "Connection problems, Please, try again"; + final Balloon balloon = + JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(connectionMessages, MessageType.ERROR, null) + .createBalloon(); + ApplicationManager.getApplication().invokeLater(() -> { + if (StudyUtils.getSelectedEditor(project) != null) { + StudyUtils.showCheckPopUp(project, balloon); + } + }); } catch (URISyntaxException e) { LOG.warn(e.getMessage()); @@ -194,8 +204,8 @@ public class EduAdaptiveStepicConnector { private static void createMockTaskFile(@NotNull Task task, String editorText) { final TaskFile taskFile = new TaskFile(); taskFile.text = editorText; - taskFile.name = "code"; - task.taskFiles.put(DEFAULT_TASK_NAME, taskFile); + taskFile.name = "code.py"; + task.taskFiles.put("code.py", taskFile); } @Nullable @@ -252,7 +262,9 @@ public class EduAdaptiveStepicConnector { request.setConfig(requestConfig); } - public static boolean postRecommendationReaction(@NotNull String lessonId, @NotNull String user, int reaction) { + public static boolean postRecommendationReaction(@NotNull String lessonId, + @NotNull String user, + int reaction) { final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATION_REACTIONS_URL); final String json = new Gson() .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); @@ -276,100 +288,74 @@ public class EduAdaptiveStepicConnector { } catch (IOException e) { LOG.warn(e.getMessage()); - return false; } + return false; } - public static void addNextRecommendedTask(@NotNull Project project, @NotNull ProgressIndicator indicator, int reactionToPost) { + public static void addNextRecommendedTask(@NotNull Project project, + @NotNull ProgressIndicator indicator, + int reaction) { final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project); final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course != null && editor != null && editor.getTaskFile() != null) { - indicator.checkCanceled(); - final StepicUser user = StudySettings.getInstance().getUser(); - if (user == null) { - LOG.warn("Can't get next recommendation: user is null"); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, - "Can't get next recommendation: you're not authorized")); - return; - } - - final Lesson adaptive = editor.getTaskFile().getTask().getLesson(); - final boolean reactionPosted = postRecommendationReaction(String.valueOf(adaptive.getId()), String.valueOf(user.getId()), reactionToPost); - - if (!reactionPosted) { - LOG.warn("Recommendation reaction wasn't posted"); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't post your reactionToPost")); - return; - } - + if (course == null || editor == null || editor.getTaskFile() == null) { + return; + } + indicator.checkCanceled(); + final StepicUser user = StepicUpdateSettings.getInstance().getUser(); + if (user == null) { + LOG.warn("Can't get next recommendation: user is null"); + ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, + "Can't get next recommendation: you're not authorized")); + return; + } + final Lesson lesson = course.getLessons().get(0); + if (lesson == null) return; + final boolean recommendationReaction = postRecommendationReaction(String.valueOf(lesson.getId()), + String.valueOf(user.getId()), reaction); + if (recommendationReaction) { indicator.checkCanceled(); final Task task = getNextRecommendation(project, course); - if (task == null) { - ApplicationManager - .getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't load a new recommendation")); - return; - } - - task.initTask(adaptive, false); - boolean replaceCurrentTask = reactionToPost == TOO_HARD_RECOMMENDATION_REACTION || reactionToPost == TOO_BORING_RECOMMENDATION_REACTION; - if (replaceCurrentTask) { - replaceCurrentTask(project, editor, task); + if (task != null) { + task.initTask(lesson, false); + if (reaction == TOO_HARD_RECOMMENDATION_REACTION || reaction == TOO_BORING_RECOMMENDATION_REACTION) { + replaceCurrentTask(project, task, lesson); + } + else { + addNewTask(project, course, task, lesson); + } } else { - addAsNextTask(project, editor, task); + ApplicationManager.getApplication().invokeLater(() -> { + final Balloon balloon = JBPopupFactory.getInstance(). + createHtmlTextBalloonBuilder("Couldn't load a new recommendation", MessageType.ERROR, null).createBalloon(); + StudyUtils.showCheckPopUp(project, balloon); + }); } - ApplicationManager.getApplication().invokeLater(() -> { VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); ProjectView.getInstance(project).refresh(); - StudyNavigator.navigateToTask(project, task); }); } + else { + LOG.warn("Recommendation reactions weren't posted"); + ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't post your reaction")); + } } - private static void replaceCurrentTask(@NotNull Project project, @NotNull StudyEditor editor, @NotNull Task task) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - final Lesson adaptive = editor.getTaskFile().getTask().getLesson(); - int taskIndex = adaptive.getTaskList().size(); - - task.setLesson(adaptive); - task.setIndex(taskIndex); - adaptive.getTaskList().set(taskIndex - 1, task); - - copyTaskFileParameters(editor, task); - - final String lessonName = EduNames.LESSON + adaptive.getIndex(); - updateCache(course, task, lessonName); - updateProjectFiles(project, task, lessonName, taskIndex); - setToolWindowText(project, task); - } - - private static void addAsNextTask(@NotNull Project project, @NotNull StudyEditor editor, @NotNull Task task) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - final Lesson adaptive = editor.getTaskFile().getTask().getLesson(); - adaptive.addTask(task); - task.setIndex(adaptive.getTaskList().size()); - adaptive.initLesson(course, true); - - final String lessonName = EduNames.LESSON + adaptive.getIndex(); - updateCache(course, task, lessonName); - createFilesForNewTask(project, task, lessonName, course.getCourseDirectory()); - } - - private static void createFilesForNewTask(@NotNull Project project, - @NotNull Task task, - @NotNull String lessonName, - @NotNull String courseDirectory) { + private static void addNewTask(@NotNull Project project, Course course, Task task, Lesson lesson) { + final String lessonName = EduNames.LESSON + String.valueOf(lesson.getIndex()); + lesson.addTask(task); + task.setIndex(lesson.getTaskList().size()); final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName); + if (lessonDir != null) { ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { try { - StudyGenerator.createTask(task, lessonDir, new File(courseDirectory, lessonName), project); + final String taskName = EduNames.TASK + String.valueOf(task.getIndex()); + StudyGenerator.createTask(task, lessonDir); + lesson.initLesson(course, true); + StudyNavigator.navigateToTask(project, lessonName, taskName); } catch (IOException e) { LOG.warn(e.getMessage()); @@ -378,56 +364,30 @@ public class EduAdaptiveStepicConnector { } } - private static void copyTaskFileParameters(@NotNull StudyEditor editor, @NotNull Task task) { - final Map taskFiles = task.getTaskFiles(); - if (taskFiles.size() == 1) { - TaskFile newTaskFile = (TaskFile)taskFiles.values().toArray()[0]; - setTaskFileParameters(editor, task, newTaskFile); - updateEditorText(editor, newTaskFile); - } - else { - LOG.warn("Got task without unexpected number of task files: " + taskFiles.size()); - } - } + private static void replaceCurrentTask(@NotNull Project project, Task task, Lesson lesson) { + final Task unsolvedTask = lesson.getTaskList().get(lesson.getTaskList().size() - 1); + final String lessonName = EduNames.LESSON + String.valueOf(lesson.getIndex()); + task.setLesson(unsolvedTask.getLesson()); + task.setIndex(unsolvedTask.getIndex()); + lesson.getTaskList().set(lesson.getTaskList().size() - 1, task); + ApplicationManager.getApplication().invokeLater(() -> StudyNavigator.navigateToTask(project, task)); - private static void setToolWindowText(@NotNull Project project, @NotNull Task task) { + final String taskName = EduNames.TASK + String.valueOf(lesson.getTaskList().size()); + final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName); + + if (lessonDir != null) { + try { + StudyGenerator.createTask(task, lessonDir); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + } final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); if (window != null) { - window.setTaskText(StudyUtils.wrapTextToDisplayLatex(task.getTaskDescription()), task.getTaskDir(project), project); + window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getTaskDescription()), unsolvedTask.getTaskDir(project), project); } - } - - private static void updateProjectFiles(@NotNull Project project, @NotNull Task task, @NotNull String lessonName, int taskIndex) { - final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName); - if (lessonDir != null) { - final File taskResourceRoot = new File(lessonDir.getCanonicalPath(), EduNames.TASK + taskIndex); - StudyProjectGenerator.createFiles(taskResourceRoot, task.getTaskTexts()); - StudyProjectGenerator.createFiles(taskResourceRoot, task.getTestsText()); - } - } - - private static void updateCache(@NotNull Course course, @NotNull Task task, @NotNull String lessonName) { - String taskName = EduNames.TASK + task.getIndex(); - final File lessonDirectory = new File(course.getCourseDirectory(), lessonName); - final File taskDir = new File(lessonDirectory, taskName); - StudyProjectGenerator.flushTask(task, taskDir); - StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); - } - - private static void updateEditorText(@NotNull StudyEditor editor, @NotNull TaskFile newTaskFile) { - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - final Document document = editor.getEditor().getDocument(); - document.setText(newTaskFile.text); - })); - } - - private static void setTaskFileParameters(@NotNull StudyEditor editor, @NotNull Task task, @NotNull TaskFile newTaskFile) { - TaskFile currentTaskFile = editor.getTaskFile(); - currentTaskFile.text = newTaskFile.text; - currentTaskFile.name = newTaskFile.name; - currentTaskFile.setTask(task); - task.getTaskFiles().clear(); - task.taskFiles.put(DEFAULT_TASK_NAME, currentTaskFile); + StudyNavigator.navigateToTask(project, lessonName, taskName); } @NotNull @@ -482,7 +442,7 @@ public class EduAdaptiveStepicConnector { taskFile.name = CODE_TASK_TYPE; final String templateForTask = getCodeTemplateForTask(project, task, step.options.codeTemplates); taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask; - task.taskFiles.put(DEFAULT_TASK_NAME, taskFile); + task.taskFiles.put("code.py", taskFile); } return task; } @@ -543,7 +503,6 @@ public class EduAdaptiveStepicConnector { return answer; } - @NotNull public static Pair checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) { int attemptId = -1; try { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java index ed8afb093f92..f511b8547d25 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java @@ -12,9 +12,10 @@ import com.intellij.util.Alarm; import com.intellij.util.text.DateFormatUtil; import com.jetbrains.edu.learning.StudySettings; import com.jetbrains.edu.learning.courseFormat.CourseInfo; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Date; import java.util.List; public class EduStepicUpdater { @@ -50,24 +51,27 @@ public class EduStepicUpdater { ActionCallback callback = new ActionCallback(); ApplicationManager.getApplication().executeOnPooledThread(() -> { final List courses = EduStepicConnector.getCourses(null); - final List cachedCourses = StudyProjectGenerator.getCoursesFromCache(); - StudyProjectGenerator.flushCache(courses); StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis()); - courses.removeAll(cachedCourses); - if (!courses.isEmpty() && !cachedCourses.isEmpty()) { + if (!courses.isEmpty()) { + List updated = new ArrayList<>(); + for (CourseInfo course : courses) { + if (course.getUpdateDate().after(new Date(StepicUpdateSettings.getInstance().getLastTimeChecked()))) { + updated.add(course); + } + } + if (updated.isEmpty()) return; final String message; final String title; - if (courses.size() == 1) { - message = courses.get(0).getName(); + if (updated.size() == 1) { + message = updated.get(0).getName(); title = "New course available"; } else { title = "New courses available"; - message = StringUtil.join(courses, CourseInfo::getName, ", "); + message = StringUtil.join(updated, CourseInfo::getName, ", "); } - final Notification notification = - new Notification("New.course", title, message, NotificationType.INFORMATION); + final Notification notification = new Notification("New.course", title, message, NotificationType.INFORMATION); notification.notify(null); } }); @@ -79,8 +83,7 @@ public class EduStepicUpdater { } private static boolean checkNeeded() { - final List courses = StudyProjectGenerator.getCoursesFromCache(); - long timeToNextCheck = StudySettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis(); - return courses.isEmpty() || timeToNextCheck <= 0; + long timeToNextCheck = StepicUpdateSettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis(); + return timeToNextCheck <= 0; } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java index b5a2065c3135..e3c20424e020 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -5,8 +5,6 @@ import com.google.gson.annotations.SerializedName; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.learning.StudyUtils; @@ -18,12 +16,10 @@ import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; @@ -89,21 +85,11 @@ public class StepicWrappers { } String name = entry.getKey(); VirtualFile answerFile = taskDir.findFileByRelativePath(name); - Pair pair = EduUtils.createStudentFile(StepicWrappers.class, project, answerFile, stepicDir, null, 0); - if (pair == null) { + TaskFile studentTaskFile = EduUtils.createStudentFile(project, answerFile, null, 0); + if (studentTaskFile == null) { return; } - VirtualFile virtualFile = pair.getFirst(); - TaskFile taskFile = pair.getSecond(); - try { - InputStream stream = virtualFile.getInputStream(); - taskFile.text = - EduUtils.isImage(name) ? Base64.encodeBase64URLSafeString(FileUtil.loadBytes(stream)) : FileUtil.loadTextAndClose(stream); - } - catch (IOException e) { - LOG.error("Can't find file " + virtualFile.getPath()); - } - source.files.add(taskFile); + source.files.add(studentTaskFile); }); } return source; diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index e236b5487fa3..ea53e5725f80 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -290,7 +290,6 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor { myGenerator.setCourses(courses); myAvailableCourses = courses; - StudyProjectGenerator.flushCache(myAvailableCourses, false); } private void addCoursesToCombobox(@NotNull List courses) { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 5a855a007786..9a2358a625a9 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -324,7 +324,6 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D int taskNum = 0; int taskSolved = 0; for (Lesson lesson : lessons) { - if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) continue; taskNum += lesson.getTaskList().size(); taskSolved += getSolvedTasks(lesson); } diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java index 69d9b4eb311d..b327a074f6b6 100644 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java +++ b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java @@ -81,7 +81,6 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { super.setUp(); Course course = new Course(); course.setName("test course"); - course.setCourseDirectory(getProject().getBasePath()); StudyTaskManager.getInstance(getProject()).setCourse(course); Lesson lesson = new Lesson(); @@ -125,7 +124,6 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { } taskFile.sortAnswerPlaceholders(); StudyUtils.drawAllAnswerPlaceholders(myFixture.getEditor(), taskFile); - CCUtils.createResourceFile(file, StudyTaskManager.getInstance(getProject()).getCourse(), file.getParent()); return file; } diff --git a/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip b/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip index 5542a7925fc0..a6293bc906a3 100644 Binary files a/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip and b/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip differ diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java index 08b023c7d613..558c12db1091 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java @@ -33,10 +33,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.io.File; - -import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.OUR_COURSES_DIR; - public class PyCCProjectGenerator extends PythonProjectGenerator { private static final Logger LOG = Logger.getInstance(PyCCProjectGenerator.class); @@ -60,11 +56,7 @@ public class PyCCProjectGenerator extends PythonProjectGenerator StudyCheckUtils.flushWindows(myTask, myTaskDir)); return super.execute(executor, processStarter, patchers); diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java index eee62abd65a9..8a8d37ebc5b0 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java @@ -14,12 +14,14 @@ import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; +import com.intellij.util.PathUtil; import com.jetbrains.edu.coursecreator.settings.CCSettings; import com.jetbrains.edu.learning.actions.StudyCheckAction; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -65,9 +67,8 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator { } @Override - public boolean excludeFromArchive(@NotNull File pathname) { - String name = pathname.getName(); - return name.contains("__pycache__") || name.contains(".pyc"); + public boolean excludeFromArchive(@NotNull String name) { + return name.contains("__pycache__") || name.endsWith(".pyc"); } @Override @@ -126,4 +127,17 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator { public StudyCheckAction getCheckAction() { return new PyStudyCheckAction(); } + + @Override + public String getBundledCoursePath() { + @NonNls String jarPath = PathUtil.getJarPathForClass(PyEduPluginConfigurator.class); + if (jarPath.endsWith(".jar")) { + final File jarFile = new File(jarPath); + + File pluginBaseDir = jarFile.getParentFile(); + return new File(new File(pluginBaseDir, "courses"), "Introduction to Python.zip").getPath(); + } + + return new File(new File(jarPath, "courses"), "Introduction to Python.zip").getPath(); + } } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 443c80f20c5e..264596c26b46 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -94,9 +94,9 @@ public class PyStudyCheckAction extends StudyCheckAction { } final Course course = myTaskManger.getCourse(); if (course != null && EduNames.STUDY.equals(course.getCourseMode())) { - CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { - StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project); - })); + CommandProcessor.getInstance().runUndoTransparentAction( + () -> ApplicationManager.getApplication().runWriteAction( + () -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project))); } } final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index a9d7e496138f..7393a5f192ba 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -23,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; import com.intellij.util.BooleanFunction; +import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.CourseInfo; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; @@ -125,7 +126,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator getCourses() { - return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject()); - } - public void setSelectedCourse(CourseInfo course) { myGenerator.setSelectedCourse(course); } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java index 922fd3bc2f1b..dc230cb66239 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java @@ -2,24 +2,18 @@ package com.jetbrains.edu.learning; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.platform.templates.github.ZipUtil; -import com.intellij.util.PathUtil; import com.intellij.util.messages.MessageBus; -import com.jetbrains.edu.learning.actions.PyStudyIntroductionCourseAction; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import org.jetbrains.annotations.NonNls; import java.io.File; -import java.io.IOException; @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"}) public class PyStudyInitialConfigurator { - private static final Logger LOG = Logger.getInstance(PyStudyInitialConfigurator.class.getName()); - @NonNls private static final String CONFIGURED_V35 = "StudyPyCharm.InitialConfiguration35"; + @NonNls private static final String CONFIGURED_V40 = "StudyPyCharm.InitialConfiguration40"; /** * @noinspection UnusedParameters @@ -29,42 +23,9 @@ public class PyStudyInitialConfigurator { final PropertiesComponent propertiesComponent, FileTypeManager fileTypeManager, final ProjectManagerEx projectManager) { - final File file = new File(getCoursesRoot(), "Introduction to Python.zip"); - if (!propertiesComponent.getBoolean(CONFIGURED_V35) && file.exists()) { - try { - File[] children = StudyProjectGenerator.OUR_COURSES_DIR.listFiles( - (dir, name) -> name.equals(PyStudyIntroductionCourseAction.INTRODUCTION_TO_PYTHON)); - if (children != null) { - for (File child : children) { - FileUtil.delete(child); - } - } - copyCourse(file, StudyProjectGenerator.OUR_COURSES_DIR); - propertiesComponent.setValue(CONFIGURED_V35, "true"); - } - catch (IOException e) { - LOG.warn("Couldn't copy bundled courses " + e); - } + if (!propertiesComponent.getBoolean(CONFIGURED_V40)) { + final File courses = new File(PathManager.getConfigPath(), "courses"); + FileUtil.delete(courses); } } - - private static void copyCourse(File bundledCourse, File userCourseDir) throws IOException { - String fileName = bundledCourse.getName(); - String unzippedName = fileName.substring(0, fileName.indexOf(".")); - File courseDir = new File(userCourseDir, unzippedName); - ZipUtil.unzip(null, courseDir, bundledCourse, null, null, true); - } - - private static File getCoursesRoot() { - @NonNls String jarPath = PathUtil.getJarPathForClass(PyStudyInitialConfigurator.class); - if (jarPath.endsWith(".jar")) { - final File jarFile = new File(jarPath); - - - File pluginBaseDir = jarFile.getParentFile(); - return new File(pluginBaseDir, "courses"); - } - - return new File(jarPath, "courses"); - } } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java index 9adf0f45dfb5..0539ea134b28 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java @@ -23,32 +23,25 @@ public class PyStudyShowTutorial extends AbstractProjectComponent { @Override public void projectOpened() { - ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { - @Override - public void run() { - ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() { - @Override - public void run() { - if (PropertiesComponent.getInstance().getBoolean(ourShowPopup, true)) { - final String content = "If you'd like to learn more about PyCharm Edu, " + - "click here to watch a tutorial"; - final Notification notification = new Notification("Watch Tutorials!", "", content, NotificationType.INFORMATION, - new NotificationListener.UrlOpeningListener(true)); - Notifications.Bus.notify(notification); - Balloon balloon = notification.getBalloon(); - if (balloon != null) { - balloon.addListener(new JBPopupAdapter() { - @Override - public void onClosed(LightweightWindowEvent event) { - notification.expire(); - } - }); + ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction( + (DumbAwareRunnable)() -> { + if (PropertiesComponent.getInstance().getBoolean(ourShowPopup, true)) { + final String content = "If you'd like to learn more about PyCharm Edu, " + + "click here to watch a tutorial"; + final Notification notification = new Notification("Watch Tutorials!", "", content, NotificationType.INFORMATION, + new NotificationListener.UrlOpeningListener(true)); + Notifications.Bus.notify(notification); + Balloon balloon = notification.getBalloon(); + if (balloon != null) { + balloon.addListener(new JBPopupAdapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + notification.expire(); } - notification.whenExpired(() -> PropertiesComponent.getInstance().setValue(ourShowPopup, false, true)); - } + }); } - }); - } - }); + notification.whenExpired(() -> PropertiesComponent.getInstance().setValue(ourShowPopup, false, true)); + } + })); } } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java index a24db818717f..6720691d37f6 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java @@ -10,7 +10,6 @@ import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.learning.checker.StudyTestRunner; import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.python.sdk.PythonSdkType; @@ -28,7 +27,6 @@ public class PyStudyTestRunner extends StudyTestRunner { public Process createCheckProcess(@NotNull final Project project, @NotNull final String executablePath) throws ExecutionException { final Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]); - Course course = myTask.getLesson().getCourse(); PyEduPluginConfigurator configurator = new PyEduPluginConfigurator(); String testsFileName = configurator.getTestFileName(); if (myTask instanceof TaskWithSubtasks) { @@ -50,8 +48,6 @@ public class PyStudyTestRunner extends StudyTestRunner { if (pythonPath != null) { commandLine.setExePath(pythonPath); commandLine.addParameter(testRunner.getPath()); - File resourceFile = new File(course.getCourseDirectory()); - commandLine.addParameter(resourceFile.getPath()); commandLine.addParameter(FileUtil.toSystemDependentName(executablePath)); return commandLine.createProcess(); } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java index 1d44f500627d..578bb361df37 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java @@ -19,9 +19,10 @@ import com.intellij.ide.impl.ProjectUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; +import com.jetbrains.edu.learning.EduPluginConfigurator; import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.courseFormat.CourseInfo; +import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep; import com.jetbrains.python.newProject.steps.PythonGenerateProjectCallback; import icons.InteractiveLearningPythonIcons; @@ -44,7 +45,8 @@ public class PyStudyIntroductionCourseAction extends AnAction { if (projectDir.exists()) { return; } - if (StudyProjectGenerator.getBundledIntro() != null) { + final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance()); + if (configurator.getBundledCoursePath() != null) { return; } Presentation presentation = e.getPresentation(); @@ -60,7 +62,9 @@ public class PyStudyIntroductionCourseAction extends AnAction { } else { final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator(); - CourseInfo introCourse = StudyProjectGenerator.getBundledIntro(); + final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance()); + final String bundledCoursePath = configurator.getBundledCoursePath(); + CourseInfo introCourse = generator.getGenerator().getCourseInfo(bundledCoursePath); if (introCourse == null) { return; } diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index 6a3be496eb18..42bdaa245c70 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -120,39 +120,6 @@ def test_is_not_empty(): failed("The file is empty. Please, reload the task and try again.") -def test_is_initial_text(error_text="You should modify the file"): - """ - Checks that file was modified - """ - if not get_answer_placeholders(): - passed() - return - path = sys.argv[-1] - text = get_initial_text(path) - file_text = get_file_text(path) - - if file_text.strip() == text.strip(): - failed(error_text) - else: - passed() - - -def get_initial_text(path): - """ - Returns the initial task text - """ - course_lib = sys.argv[-2] - test_dir = sys.argv[-3] - import os - lesson_task = os.sep.join(test_dir.split(os.sep)[-3:-1]) - task_dir = os.sep.join(test_dir.split(os.sep)[:-1]) - relative_path = lesson_task + os.sep + path[len(task_dir):] - - # path format is "project_root/lessonX/taskY/.../file.py" - initial_file_path = os.path.join(course_lib, relative_path) - return get_file_text(initial_file_path) - - def test_text_equals(text, error_text): """ Checks that answer equals text. @@ -259,7 +226,6 @@ def do_not_run_on_check(): def run_common_tests(error_text="Please, reload file and try again"): - test_is_initial_text() test_is_not_empty() test_answer_placeholders_text_deleted() test_file_importable() diff --git a/python/educational-python/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java b/python/educational-python/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java index 8a5292e57cc9..ffc8c6372f42 100644 --- a/python/educational-python/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java +++ b/python/educational-python/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java @@ -71,7 +71,7 @@ public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider { return PlatformUtils.isPyCharmEducational() ? text : actions.get(0).getTemplatePresentation().getText(); }, null, GutterIconRenderer.Alignment.RIGHT) { - @Nullable + @Override public GutterIconRenderer createGutterRenderer() { return new LineMarkerGutterIconRenderer(this) { diff --git a/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java b/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java index 1f191c949548..1e348cf533eb 100644 --- a/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java +++ b/python/educational-python/src/com/jetbrains/python/edu/debugger/PyEduConsoleInputFilterProvider.java @@ -7,7 +7,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.jetbrains.python.debugger.PyRunCythonExtensionsFilter; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -17,7 +16,6 @@ public class PyEduConsoleInputFilterProvider implements ConsoleInputFilterProvid @Override public InputFilter[] getDefaultFilters(@NotNull Project project) { return new InputFilter[]{new InputFilter() { - @Nullable @Override public List> applyFilter(String text, ConsoleViewContentType outputType) { if (outputType.equals(ConsoleViewContentType.SYSTEM_OUTPUT) && !text.contains("exit code")) {