mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
EDU-894 Save all course content in course.json
EDU-891 Drop courses cache First iteration
This commit is contained in:
committed by
Ekaterina Tuzova
parent
6f92410b5f
commit
2168b64066
@@ -198,7 +198,6 @@
|
||||
<checkListener implementation="com.jetbrains.edu.learning.twitter.StudyTwitterAction"/>
|
||||
|
||||
<!--course creator-->
|
||||
<checkListener implementation="com.jetbrains.edu.coursecreator.CCCheckListener"/>
|
||||
<optionsProvider instance="com.jetbrains.edu.coursecreator.settings.CCOptions"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+1
-19
@@ -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
|
||||
|
||||
@@ -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<VirtualFile> 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<String, TaskFile> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -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) {
|
||||
|
||||
+4
-6
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+72
-64
@@ -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<VirtualFile> isSubtaskTestFile =
|
||||
file -> CCUtils.isTestsFile(project, file) && file.getName().contains(EduNames.SUBTASK_MARKER);
|
||||
List<VirtualFile> subtaskTests = ContainerUtil.filter(Arrays.asList(studentFileDir.getChildren()), isSubtaskTestFile);
|
||||
for (VirtualFile subtaskTest : subtaskTests) {
|
||||
private void convertToStudentTaskFiles(Task task, VirtualFile taskDir) {
|
||||
final HashMap<String, TaskFile> studentTaskFiles = new HashMap<>();
|
||||
for (Map.Entry<String, TaskFile> 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<VirtualFile> 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<VirtualFile> 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<VirtualFile> getTestFiles(@NotNull Task task, @NotNull Project project) {
|
||||
List<VirtualFile> 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<VirtualFile> getDescriptionFiles(@NotNull Task task, @NotNull Project project) {
|
||||
List<VirtualFile> 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) {
|
||||
|
||||
+4
-5
@@ -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;
|
||||
|
||||
+6
-13
@@ -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;
|
||||
|
||||
+5
-24
@@ -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<String, TaskFile> taskFiles, TaskFile taskFile) {
|
||||
public static void hideFromStudent(VirtualFile file, Project project, Map<String, TaskFile> 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);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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<Integer> sections = info.getSections();
|
||||
final Integer sectionId = sections.get(sections.size() - 1);
|
||||
|
||||
+4
-4
@@ -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<Integer> sections = info.getSections();
|
||||
final Integer sectionId = sections.get(sections.size()-1);
|
||||
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
|
||||
|
||||
+8
-17
@@ -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<VirtualFile, TaskFile> 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();
|
||||
|
||||
-4
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+26
-52
@@ -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<VirtualFile> 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();
|
||||
|
||||
@@ -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<EduPluginConfigurator> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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<Element>, Dumb
|
||||
public int VERSION = 5;
|
||||
|
||||
public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
|
||||
public List<String> myInvisibleFiles = new ArrayList<>();
|
||||
|
||||
public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx();
|
||||
private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT;
|
||||
@@ -150,12 +147,6 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, 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<Element>, 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;
|
||||
}
|
||||
|
||||
@@ -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<String, TaskFile> taskFiles = firstTask.getTaskFiles();
|
||||
VirtualFile activeVirtualFile = null;
|
||||
for (Map.Entry<String, TaskFile> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-22
@@ -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
|
||||
|
||||
+10
-18
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VirtualFile, TaskFile> 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) {
|
||||
|
||||
@@ -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<Lesson> lessons = new ArrayList<>();
|
||||
@Expose private List<StepicUser> 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<Lesson> getLessons() {
|
||||
return lessons;
|
||||
return getLessons(false);
|
||||
}
|
||||
|
||||
public List<Lesson> getLessons(boolean withAadditional) {
|
||||
return withAadditional ? lessons
|
||||
: lessons.stream().filter(lesson -> !EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void setLessons(List<Lesson> lessons) {
|
||||
this.lessons = lessons;
|
||||
}
|
||||
|
||||
public void addLessons(List<Lesson> 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<Lesson> 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;
|
||||
}
|
||||
|
||||
@@ -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<CourseInfo> 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<Integer> getSections() {
|
||||
|
||||
@@ -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<String, TaskFile> taskFiles = new HashMap<>();
|
||||
|
||||
protected Map<String, String> testsText = new HashMap<>();
|
||||
protected Map<String, String> taskTexts = new HashMap<>();
|
||||
@SerializedName("test_files")
|
||||
@Expose protected Map<String, String> testsText = new HashMap<>();
|
||||
@SerializedName("task_texts")
|
||||
@Expose protected Map<String, String> taskTexts = new HashMap<>();
|
||||
|
||||
@Transient private Lesson myLesson;
|
||||
@Expose @SerializedName("update_date") private Date myUpdateDate;
|
||||
|
||||
+92
-100
@@ -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<String, TaskFile> 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<Task> 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<Lesson> lessons = course.getLessons();
|
||||
final List<Lesson> 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<Task> 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<String, TaskFile> 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<String, String> texts = task.getTaskTexts();
|
||||
for (Map.Entry<String, String> 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<String, String> tests = task.getTestsText();
|
||||
for (Map.Entry<String, String> 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<Task> taskList = lesson.getTaskList();
|
||||
if (taskList.size() != 1) return;
|
||||
final Task task = taskList.get(0);
|
||||
for (Map.Entry<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-450
@@ -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<SettingsListener> myListeners = ContainerUtil.newArrayList();
|
||||
private List<CourseInfo> myCourses = new ArrayList<>();
|
||||
private List<Course> myLocalCourses = new ArrayList<>();
|
||||
private List<Integer> 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<String, TaskFile> taskFiles = firstTask.getTaskFiles();
|
||||
VirtualFile activeVirtualFile = null;
|
||||
for (Map.Entry<String, TaskFile> 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<Lesson> 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<Task> taskList = lesson.getTaskList();
|
||||
if (taskList.size() != 1) return;
|
||||
final Task task = taskList.get(0);
|
||||
for (Map.Entry<String, String> 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<String, TaskFile> 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<String, String> files) {
|
||||
for (Map.Entry<String, String> 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<CourseInfo> courses) {
|
||||
flushCache(courses, true);
|
||||
}
|
||||
|
||||
public static void flushCache(List<CourseInfo> 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<CourseInfo> 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<CourseInfo> 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<EduPluginConfigurator>[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null);
|
||||
for (LanguageExtensionPoint<EduPluginConfigurator> extension : extensions) {
|
||||
final EduPluginConfigurator configurator = extension.getInstance();
|
||||
final String path = configurator.getBundledCoursePath();
|
||||
if (path != null) {
|
||||
return getCourseInfo(path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<CourseInfo> getCoursesFromCache() {
|
||||
List<CourseInfo> 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<CourseInfo> 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<StepicUser> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-3
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -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<String, UsageDescriptor> myUsageDescriptors = new FactoryMap<String, UsageDescriptor>() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected UsageDescriptor create(String key) {
|
||||
return new UsageDescriptor(key, 0);
|
||||
|
||||
+90
-131
@@ -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<String, TaskFile> 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<Boolean, String> checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) {
|
||||
int attemptId = -1;
|
||||
try {
|
||||
|
||||
+16
-13
@@ -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<CourseInfo> courses = EduStepicConnector.getCourses(null);
|
||||
final List<CourseInfo> cachedCourses = StudyProjectGenerator.getCoursesFromCache();
|
||||
StudyProjectGenerator.flushCache(courses);
|
||||
StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis());
|
||||
|
||||
courses.removeAll(cachedCourses);
|
||||
if (!courses.isEmpty() && !cachedCourses.isEmpty()) {
|
||||
if (!courses.isEmpty()) {
|
||||
List<CourseInfo> 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<CourseInfo> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VirtualFile, TaskFile> 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;
|
||||
|
||||
@@ -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<CourseInfo> courses) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
+1
-12
@@ -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<PyNewProjectSettings> {
|
||||
private static final Logger LOG = Logger.getInstance(PyCCProjectGenerator.class);
|
||||
@@ -60,11 +56,7 @@ public class PyCCProjectGenerator extends PythonProjectGenerator<PyNewProjectSet
|
||||
@NotNull PyNewProjectSettings settings,
|
||||
@NotNull Module module,
|
||||
@Nullable final PyProjectSynchronizer synchronizer) {
|
||||
generateProject(project, baseDir, mySettingsPanel);
|
||||
}
|
||||
|
||||
public static void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, CCNewProjectPanel settingsPanel) {
|
||||
final Course course = getCourse(project, settingsPanel);
|
||||
final Course course = getCourse(project, mySettingsPanel);
|
||||
EduUsagesCollector.projectTypeCreated(CCUtils.COURSE_MODE);
|
||||
|
||||
final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir);
|
||||
@@ -107,9 +99,6 @@ public class PyCCProjectGenerator extends PythonProjectGenerator<PyNewProjectSet
|
||||
course.setLanguage(language);
|
||||
course.setCourseMode(CCUtils.COURSE_MODE);
|
||||
|
||||
File courseDir = new File(OUR_COURSES_DIR, name + "-" + project.getName());
|
||||
course.setCourseDirectory(courseDir.getPath());
|
||||
|
||||
StudyTaskManager.getInstance(project).setCourse(course);
|
||||
StudyUtils.registerStudyToolWindow(course, project);
|
||||
return course;
|
||||
|
||||
+1
-5
@@ -15,20 +15,18 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.jetbrains.edu.coursecreator.CCUtils;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.checker.StudyCheckUtils;
|
||||
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.tasks.Task;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
|
||||
import com.jetbrains.python.run.CommandLinePatcher;
|
||||
import com.jetbrains.python.run.PythonCommandLineState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public class PyCCCommandLineState extends PythonCommandLineState {
|
||||
@@ -60,7 +58,6 @@ public class PyCCCommandLineState extends PythonCommandLineState {
|
||||
assert course != null;
|
||||
|
||||
group.addParameter(myRunConfiguration.getPathToTest());
|
||||
group.addParameter(new File(course.getCourseDirectory()).getPath());
|
||||
|
||||
String path = getCurrentTaskFilePath();
|
||||
if (path != null) {
|
||||
@@ -97,7 +94,6 @@ public class PyCCCommandLineState extends PythonCommandLineState {
|
||||
|
||||
@Override
|
||||
public ExecutionResult execute(Executor executor, PythonProcessStarter processStarter, CommandLinePatcher... patchers) throws ExecutionException {
|
||||
CCUtils.updateResources(myRunConfiguration.getProject(), myTask, myTaskDir);
|
||||
ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, myTaskDir));
|
||||
|
||||
return super.execute(executor, processStarter, patchers);
|
||||
|
||||
+17
-3
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+2
-6
@@ -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<PyN
|
||||
@NotNull Module module,
|
||||
@Nullable PyProjectSynchronizer synchronizer) {
|
||||
myGenerator.generateProject(project, baseDir);
|
||||
final String testHelper = "test_helper.py";
|
||||
final String testHelper = EduNames.TEST_HELPER;
|
||||
if (baseDir.findChild(testHelper) != null) return;
|
||||
final FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("test_helper");
|
||||
final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir);
|
||||
@@ -160,11 +161,6 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
return mySettingsPanel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<CourseInfo> getCourses() {
|
||||
return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
|
||||
}
|
||||
|
||||
public void setSelectedCourse(CourseInfo course) {
|
||||
myGenerator.setSelectedCourse(course);
|
||||
}
|
||||
|
||||
+5
-44
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+18
-25
@@ -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 = "<html>If you'd like to learn more about PyCharm Edu, " +
|
||||
"click <a href=\"https://www.jetbrains.com/pycharm-edu/quickstart/\">here</a> to watch a tutorial</html>";
|
||||
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 = "<html>If you'd like to learn more about PyCharm Edu, " +
|
||||
"click <a href=\"https://www.jetbrains.com/pycharm-edu/quickstart/\">here</a> to watch a tutorial</html>";
|
||||
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));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -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();
|
||||
}
|
||||
|
||||
+7
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
+1
-1
@@ -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<PsiElement>(this) {
|
||||
|
||||
-2
@@ -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<Pair<String, ConsoleViewContentType>> applyFilter(String text, ConsoleViewContentType outputType) {
|
||||
if (outputType.equals(ConsoleViewContentType.SYSTEM_OUTPUT) && !text.contains("exit code")) {
|
||||
|
||||
Reference in New Issue
Block a user