mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master' into meanmail/EDU-839
This commit is contained in:
+1
-1
@@ -38,7 +38,7 @@
|
||||
</target>
|
||||
<!--
|
||||
<target name="test" depends="init">
|
||||
<call_gant script="${project.home}/build/scripts/tests.gant"/>
|
||||
<call_gant script="${project.home}/build/scripts/tests_in_community.gant"/>
|
||||
</target>
|
||||
-->
|
||||
|
||||
|
||||
@@ -122,9 +122,9 @@
|
||||
<reference id="CC.NewStep"/>
|
||||
<separator/>
|
||||
<action id="PackCourse" class="com.jetbrains.edu.coursecreator.actions.CCCreateCourseArchive"/>
|
||||
<action id="PushTask" class="com.jetbrains.edu.coursecreator.actions.CCPushTask"/>
|
||||
<action id="PushLesson" class="com.jetbrains.edu.coursecreator.actions.CCPushLesson"/>
|
||||
<action id="PushCourse" class="com.jetbrains.edu.coursecreator.actions.CCPushCourse"/>
|
||||
<action id="PushTask" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushTask"/>
|
||||
<action id="PushLesson" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushLesson"/>
|
||||
<action id="PushCourse" class="com.jetbrains.edu.coursecreator.actions.stepik.CCPushCourse"/>
|
||||
<reference id="ChangeCourseInfo"/>
|
||||
<add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
|
||||
</group>
|
||||
@@ -154,7 +154,7 @@
|
||||
</group>
|
||||
|
||||
<action id="UnpackCourse" class="com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive"/>
|
||||
<action id="GetCourse" class="com.jetbrains.edu.coursecreator.actions.CCGetCourseFromStepic"/>
|
||||
<action id="GetCourse" class="com.jetbrains.edu.coursecreator.actions.stepik.CCGetCourseFromStepic"/>
|
||||
|
||||
</actions>
|
||||
|
||||
@@ -204,7 +204,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(file.getPath()))) {
|
||||
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(course.getLessons().size());
|
||||
return lesson;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-9
@@ -4,21 +4,21 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileAdapter;
|
||||
import com.intellij.openapi.vfs.VirtualFileEvent;
|
||||
import com.intellij.openapi.vfs.VirtualFileListener;
|
||||
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.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 java.io.File;
|
||||
|
||||
public class CCVirtualFileListener extends VirtualFileAdapter {
|
||||
public class CCVirtualFileListener implements VirtualFileListener {
|
||||
|
||||
@Override
|
||||
public void fileCreated(@NotNull VirtualFileEvent event) {
|
||||
@@ -48,7 +48,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
|
||||
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.getPath())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,9 +68,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
CCUtils.createResourceFile(createdFile, course, taskVF);
|
||||
|
||||
task.addTaskFile(taskRelativePath, 1);
|
||||
task.addTaskFile(taskRelativePath, task.getTaskFiles().size()+1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +87,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter {
|
||||
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 extends VirtualFileAdapter {
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
+74
-65
@@ -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,27 @@ 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);
|
||||
courseCopy.sortLessons();
|
||||
createAdditionalFiles(courseCopy);
|
||||
generateJson(archiveFolder, courseCopy);
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
|
||||
packCourse(archiveFolder, locationDir, zipName, showMessage);
|
||||
synchronize(project);
|
||||
}
|
||||
|
||||
private void createAdditionalFiles(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 +120,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) {
|
||||
@@ -212,7 +221,7 @@ public class CCCreateCourseArchive extends DumbAwareAction {
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
private static void generateJson(VirtualFile parentDir, Course course) {
|
||||
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
|
||||
registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskSerializer()).create();
|
||||
registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskAdapter()).create();
|
||||
final String json = gson.toJson(course);
|
||||
final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE);
|
||||
OutputStreamWriter outputStreamWriter = null;
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class CCCreateLesson extends CCCreateStudyItemActionBase {
|
||||
|
||||
@Override
|
||||
protected List<? extends StudyItem> getSiblings(@NotNull Course course, @Nullable StudyItem parentItem) {
|
||||
return course.getLessons();
|
||||
return course.getLessons(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+72
-101
@@ -1,16 +1,12 @@
|
||||
package com.jetbrains.edu.coursecreator.actions;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.projectView.ProjectView;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
@@ -21,25 +17,23 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.platform.templates.github.ZipUtil;
|
||||
import com.jetbrains.edu.coursecreator.CCUtils;
|
||||
import com.jetbrains.edu.learning.StudySerializationUtils;
|
||||
import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
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 com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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());
|
||||
|
||||
public CCFromCourseArchive() {
|
||||
super("Unpack Course Archive", "Unpack Course Archive", AllIcons.FileTypes.Archive);
|
||||
}
|
||||
@@ -54,7 +48,8 @@ public class CCFromCourseArchive extends DumbAwareAction {
|
||||
}
|
||||
|
||||
private static void unpackCourseArchive(final Project project) {
|
||||
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, true, true, true, false);
|
||||
FileChooserDescriptor descriptor =
|
||||
new FileChooserDescriptor(true, true, true, true, true, false);
|
||||
|
||||
final VirtualFile virtualFile = FileChooser.chooseFile(descriptor, project, null);
|
||||
if (virtualFile == null) {
|
||||
@@ -62,62 +57,51 @@ public class CCFromCourseArchive extends DumbAwareAction {
|
||||
}
|
||||
final String basePath = project.getBasePath();
|
||||
if (basePath == null) return;
|
||||
Reader reader = null;
|
||||
try {
|
||||
ZipUtil.unzip(null, new File(basePath), new File(virtualFile.getPath()), null, null, true);
|
||||
File courseMetaFile = new File(basePath, EduNames.COURSE_META_FILE);
|
||||
reader = new InputStreamReader(new FileInputStream(courseMetaFile));
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseMetaFile))
|
||||
.registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskDeserializer())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.create();
|
||||
Course course = gson.fromJson(reader, Course.class);
|
||||
|
||||
if (course == null) {
|
||||
Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course");
|
||||
return;
|
||||
}
|
||||
Course course = StudyProjectGenerator.getCourse(virtualFile.getPath());
|
||||
if (course == null) {
|
||||
Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course");
|
||||
return;
|
||||
}
|
||||
generateFromStudentCourse(project, course);
|
||||
}
|
||||
|
||||
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;
|
||||
int taskIndex = 1;
|
||||
for (Lesson lesson : course.getLessons()) {
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
|
||||
lesson.setIndex(index);
|
||||
if (lessonDir == null) continue;
|
||||
for (Task task : lesson.getTaskList()) {
|
||||
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
|
||||
task.setIndex(taskIndex);
|
||||
task.setLesson(lesson);
|
||||
if (taskDir == null) continue;
|
||||
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
ApplicationManager.getApplication().runWriteAction(() -> createAnswerFile(project, taskDir, entry));
|
||||
}
|
||||
taskIndex += 1;
|
||||
}
|
||||
index += 1;
|
||||
taskIndex = 1;
|
||||
public static void generateFromStudentCourse(Project project, Course course) {
|
||||
StudyTaskManager.getInstance(project).setCourse(course);
|
||||
course.setCourseMode(CCUtils.COURSE_MODE);
|
||||
final VirtualFile baseDir = project.getBaseDir();
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
|
||||
application.invokeAndWait(() -> application.runWriteAction(() -> {
|
||||
final VirtualFile[] children = baseDir.getChildren();
|
||||
for (VirtualFile child : children) {
|
||||
StudyUtils.deleteFile(child);
|
||||
}
|
||||
course.initCourse(true);
|
||||
}
|
||||
catch (JsonSyntaxException | IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e.getMessage());
|
||||
StudyGenerator.createCourse(course, baseDir);
|
||||
}));
|
||||
baseDir.refresh(false, true);
|
||||
|
||||
int index = 1;
|
||||
int taskIndex = 1;
|
||||
for (Lesson lesson : course.getLessons()) {
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
|
||||
lesson.setIndex(index);
|
||||
if (lessonDir == null) continue;
|
||||
for (Task task : lesson.getTaskList()) {
|
||||
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
|
||||
task.setIndex(taskIndex);
|
||||
task.setLesson(lesson);
|
||||
if (taskDir == null) continue;
|
||||
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
application.invokeAndWait(() -> application.runWriteAction(() -> createAnswerFile(project, taskDir, entry)));
|
||||
}
|
||||
taskIndex += 1;
|
||||
}
|
||||
index += 1;
|
||||
taskIndex = 1;
|
||||
}
|
||||
course.initCourse(true);
|
||||
application.invokeAndWait(() -> StudyUtils.registerStudyToolWindow(course, project));
|
||||
synchronize(project);
|
||||
}
|
||||
|
||||
@@ -128,57 +112,44 @@ public class CCFromCourseArchive extends DumbAwareAction {
|
||||
final TaskFile taskFile = taskFileEntry.getValue();
|
||||
VirtualFile file = userFileDir.findFileByRelativePath(name);
|
||||
assert file != null;
|
||||
final Document originDocument = FileDocumentManager.getInstance().getDocument(file);
|
||||
if (originDocument == null) {
|
||||
return;
|
||||
}
|
||||
final Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
if (document == null) return;
|
||||
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> document.replaceString(0, document.getTextLength(), originDocument.getCharsSequence())), "Create answer document", "Create answer document");
|
||||
CommandProcessor.getInstance().executeCommand(project,() -> ApplicationManager.getApplication().runWriteAction(
|
||||
() -> document.replaceString(0, document.getTextLength(), document.getCharsSequence())),
|
||||
"Create answer document", "Create answer document");
|
||||
EduDocumentListener listener = new EduDocumentListener(taskFile, false);
|
||||
document.addDocumentListener(listener);
|
||||
taskFile.sortAnswerPlaceholders();
|
||||
for (int i = taskFile.getActivePlaceholders().size() - 1; i >= 0; i--) {
|
||||
final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i);
|
||||
replaceAnswerPlaceholder(project, document, answerPlaceholder);
|
||||
|
||||
for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) {
|
||||
replaceAnswerPlaceholder(document, placeholder);
|
||||
}
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveDocument(document)), "x", "qwe");
|
||||
for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
|
||||
placeholder.setUseLength(false);
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(
|
||||
() -> FileDocumentManager.getInstance().saveDocument(document)),"Create answer document", "Create answer document");
|
||||
document.removeDocumentListener(listener);
|
||||
}
|
||||
|
||||
private static void replaceAnswerPlaceholder(@NotNull final Project project,
|
||||
@NotNull final Document document,
|
||||
@NotNull final AnswerPlaceholder answerPlaceholder) {
|
||||
final int offset = answerPlaceholder.getOffset();
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
final String text = document.getText(TextRange.create(offset, offset + answerPlaceholder.getRealLength()));
|
||||
answerPlaceholder.setTaskText(text);
|
||||
answerPlaceholder.init();
|
||||
final VirtualFile hints = project.getBaseDir().findChild(EduNames.HINTS);
|
||||
if (hints != null) {
|
||||
final ArrayList<String> result = new ArrayList<>();
|
||||
for (String hint : answerPlaceholder.getHints()) {
|
||||
final VirtualFile virtualFile = hints.findChild(hint);
|
||||
if (virtualFile != null) {
|
||||
final Document hintDocument = FileDocumentManager.getInstance().getDocument(virtualFile);
|
||||
if (hintDocument != null) {
|
||||
final String hintText = hintDocument.getText();
|
||||
result.add(hintText);
|
||||
}
|
||||
}
|
||||
}
|
||||
answerPlaceholder.setHints(result);
|
||||
}
|
||||
document.replaceString(offset, offset + answerPlaceholder.getRealLength(), answerPlaceholder.getPossibleAnswer());
|
||||
answerPlaceholder.setUseLength(false);
|
||||
private static void replaceAnswerPlaceholder(@NotNull final Document document,
|
||||
@NotNull final AnswerPlaceholder placeholder) {
|
||||
final int offset = placeholder.getOffset();
|
||||
final String text = document.getText(TextRange.create(offset, offset + placeholder.getRealLength()));
|
||||
placeholder.setTaskText(text);
|
||||
placeholder.init();
|
||||
String replacementText = placeholder.getPossibleAnswer();
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
document.replaceString(offset, offset + placeholder.getRealLength(), replacementText);
|
||||
FileDocumentManager.getInstance().saveDocument(document);
|
||||
}), "Replace answer placeholder", "From Course Archive");
|
||||
}));
|
||||
}
|
||||
|
||||
private static void synchronize(@NotNull final Project project) {
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
|
||||
ProjectView.getInstance(project).refresh();
|
||||
}
|
||||
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
package com.jetbrains.edu.coursecreator.actions;
|
||||
|
||||
import com.intellij.ide.IdeView;
|
||||
import com.intellij.ide.projectView.ProjectView;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
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.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.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 {
|
||||
|
||||
public CCGetCourseFromStepic() {
|
||||
super("Get Course From Stepik", "Get Course From Stepik", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
|
||||
final Project project = e.getData(CommonDataKeys.PROJECT);
|
||||
if (view == null || project == null) {
|
||||
return;
|
||||
}
|
||||
final String courseId = Messages.showInputDialog("Please, enter course id", "Get Course From Stepik", null);
|
||||
if (StringUtil.isNotEmpty(courseId)) {
|
||||
ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Creating Course", true) {
|
||||
@Override
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
createCourse(project, courseId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void createCourse(Project project, String courseId) {
|
||||
final VirtualFile baseDir = project.getBaseDir();
|
||||
final CourseInfo info = CCStepicConnector.getCourseInfo(courseId);
|
||||
if (info == null) return;
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
|
||||
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;
|
||||
int taskIndex = 1;
|
||||
for (Lesson lesson : course.getLessons()) {
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index));
|
||||
lesson.setIndex(index);
|
||||
if (lessonDir == null) continue;
|
||||
for (Task task : lesson.getTaskList()) {
|
||||
final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex));
|
||||
task.setIndex(taskIndex);
|
||||
task.setLesson(lesson);
|
||||
if (taskDir == null) continue;
|
||||
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
|
||||
ApplicationManager.getApplication()
|
||||
.invokeAndWait(() -> ApplicationManager.getApplication().runWriteAction(() -> createAnswerFile(project, taskDir, entry)));
|
||||
}
|
||||
taskIndex += 1;
|
||||
}
|
||||
index += 1;
|
||||
taskIndex = 1;
|
||||
}
|
||||
course.initCourse(true);
|
||||
ApplicationManager.getApplication()
|
||||
.invokeAndWait(() -> StudyUtils.registerStudyToolWindow(course, project));
|
||||
}
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
|
||||
ProjectView.getInstance(project).refresh();
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
|
||||
|
||||
+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();
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.jetbrains.edu.coursecreator.actions.stepik;
|
||||
|
||||
import com.intellij.ide.IdeView;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive;
|
||||
import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CCGetCourseFromStepic extends DumbAwareAction {
|
||||
|
||||
public CCGetCourseFromStepic() {
|
||||
super("Get Course From Stepik", "Get Course From Stepik", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
|
||||
final Project project = e.getData(CommonDataKeys.PROJECT);
|
||||
if (view == null || project == null) {
|
||||
return;
|
||||
}
|
||||
final String courseId = Messages.showInputDialog("Please, enter course id", "Get Course From Stepik", null);
|
||||
if (StringUtil.isNotEmpty(courseId)) {
|
||||
ProgressManager.getInstance().run(new Task.Modal(project, "Creating Course", true) {
|
||||
@Override
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
createCourse(project, courseId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void createCourse(Project project, String courseId) {
|
||||
final RemoteCourse info = CCStepicConnector.getCourseInfo(courseId);
|
||||
if (info == null) return;
|
||||
|
||||
final Course course = EduStepicConnector.getCourse(project, info);
|
||||
if (course == null) return;
|
||||
|
||||
CCFromCourseArchive.generateFromStudentCourse(project, course);
|
||||
}
|
||||
}
|
||||
+9
-13
@@ -1,4 +1,4 @@
|
||||
package com.jetbrains.edu.coursecreator.actions;
|
||||
package com.jetbrains.edu.coursecreator.actions.stepik;
|
||||
|
||||
import com.intellij.ide.IdeView;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
@@ -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.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
|
||||
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;
|
||||
@@ -33,11 +33,8 @@ public class CCPushCourse extends DumbAwareAction {
|
||||
presentation.setEnabledAndVisible(project != null && CCUtils.isCourseCreator(project));
|
||||
if (project != null) {
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course != null) {
|
||||
final int id = course.getId();
|
||||
if (id > 0) {
|
||||
presentation.setText("Update Course on Stepik");
|
||||
}
|
||||
if (course instanceof RemoteCourse) {
|
||||
presentation.setText("Update Course on Stepik");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,19 +50,18 @@ public class CCPushCourse extends DumbAwareAction {
|
||||
if (course == null) {
|
||||
return;
|
||||
}
|
||||
if (course.getId() > 0) {
|
||||
if (course instanceof RemoteCourse) {
|
||||
ProgressManager.getInstance().run(new Task.Modal(project, "Updating Course", true) {
|
||||
@Override
|
||||
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 List<Integer> sections = ((RemoteCourse)course).getSections();
|
||||
final Integer sectionId = sections.get(sections.size() - 1);
|
||||
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
|
||||
}
|
||||
+9
-10
@@ -1,4 +1,4 @@
|
||||
package com.jetbrains.edu.coursecreator.actions;
|
||||
package com.jetbrains.edu.coursecreator.actions.stepik;
|
||||
|
||||
import com.intellij.ide.IdeView;
|
||||
import com.intellij.ide.util.DirectoryChooserUtil;
|
||||
@@ -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.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicNames;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CCPushLesson extends DumbAwareAction {
|
||||
return;
|
||||
}
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course == null) {
|
||||
if (course == null || !(course instanceof RemoteCourse)) {
|
||||
return;
|
||||
}
|
||||
if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return;
|
||||
@@ -45,7 +45,7 @@ public class CCPushLesson extends DumbAwareAction {
|
||||
return;
|
||||
}
|
||||
final Lesson lesson = course.getLesson(lessonDir.getName());
|
||||
if (lesson != null && course.getId() > 0) {
|
||||
if (lesson != null && ((RemoteCourse)course).getId() > 0) {
|
||||
e.getPresentation().setEnabledAndVisible(true);
|
||||
if (lesson.getId() <= 0) {
|
||||
e.getPresentation().setText("Upload Lesson to Stepik");
|
||||
@@ -61,7 +61,7 @@ public class CCPushLesson extends DumbAwareAction {
|
||||
return;
|
||||
}
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course == null) {
|
||||
if (course == null || !(course instanceof RemoteCourse)) {
|
||||
return;
|
||||
}
|
||||
PsiDirectory lessonDir = DirectoryChooserUtil.getOrChooseDirectory(view);
|
||||
@@ -77,12 +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 List<Integer> sections = info.getSections();
|
||||
final int lessonId = CCStepicConnector.postLesson(project, lesson);
|
||||
final List<Integer> sections = ((RemoteCourse)course).getSections();
|
||||
final Integer sectionId = sections.get(sections.size()-1);
|
||||
CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId);
|
||||
}
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
package com.jetbrains.edu.coursecreator.actions;
|
||||
package com.jetbrains.edu.coursecreator.actions.stepik;
|
||||
|
||||
import com.intellij.ide.IdeView;
|
||||
import com.intellij.ide.util.DirectoryChooserUtil;
|
||||
@@ -12,11 +12,12 @@ 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.core.EduNames;
|
||||
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.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicNames;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -34,7 +35,7 @@ public class CCPushTask extends DumbAwareAction {
|
||||
return;
|
||||
}
|
||||
final Course course = StudyTaskManager.getInstance(project).getCourse();
|
||||
if (course == null) {
|
||||
if (course == null || !(course instanceof RemoteCourse)) {
|
||||
return;
|
||||
}
|
||||
if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return;
|
||||
@@ -45,7 +46,7 @@ public class CCPushTask extends DumbAwareAction {
|
||||
final PsiDirectory lessonDir = taskDir.getParentDirectory();
|
||||
if (lessonDir == null) return;
|
||||
final Lesson lesson = course.getLesson(lessonDir.getName());
|
||||
if (lesson != null && lesson.getId() > 0 && course.getId() > 0) {
|
||||
if (lesson != null && lesson.getId() > 0 && ((RemoteCourse)course).getId() > 0) {
|
||||
e.getPresentation().setEnabledAndVisible(true);
|
||||
final com.jetbrains.edu.learning.courseFormat.tasks.Task task = lesson.getTask(taskDir.getName());
|
||||
if (task.getStepId() <= 0) {
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class CCLessonMoveHandlerDelegate extends MoveHandlerDelegate {
|
||||
newItemIndex - 1, EduNames.LESSON, 1);
|
||||
|
||||
sourceLesson.setIndex(newItemIndex);
|
||||
Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
|
||||
Collections.sort(course.getLessons(true), EduUtils.INDEX_COMPARATOR);
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
-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;
|
||||
}
|
||||
|
||||
+36
-58
@@ -8,23 +8,19 @@ 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.StudyTaskManager;
|
||||
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;
|
||||
import com.jetbrains.edu.learning.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
|
||||
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;
|
||||
@@ -48,7 +44,7 @@ public class CCStepicConnector {
|
||||
private CCStepicConnector() {
|
||||
}
|
||||
|
||||
public static CourseInfo getCourseInfo(String courseId) {
|
||||
public static RemoteCourse getCourseInfo(String courseId) {
|
||||
final String url = EduStepicNames.COURSES + "/" + courseId;
|
||||
try {
|
||||
final StepicWrappers.CoursesContainer coursesContainer =
|
||||
@@ -65,13 +61,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();
|
||||
@@ -105,60 +104,33 @@ public class CCStepicConnector {
|
||||
LOG.error("Failed to push " + responseString);
|
||||
return;
|
||||
}
|
||||
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
|
||||
course.setId(postedCourse.getId());
|
||||
final RemoteCourse postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
|
||||
postedCourse.setLessons(course.getLessons(true));
|
||||
postedCourse.setAuthors(course.getAuthors());
|
||||
postedCourse.setCourseMode(CCUtils.COURSE_MODE);
|
||||
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()));
|
||||
StudyTaskManager.getInstance(project).setCourse(postedCourse);
|
||||
}
|
||||
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 +227,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));
|
||||
@@ -273,13 +245,16 @@ public class CCStepicConnector {
|
||||
LOG.error("Failed to push " + responseString);
|
||||
return -1;
|
||||
}
|
||||
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
|
||||
final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons().get(0);
|
||||
for (Integer step : postedLesson.steps) {
|
||||
deleteTask(step);
|
||||
}
|
||||
|
||||
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 +265,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));
|
||||
@@ -308,10 +283,13 @@ public class CCStepicConnector {
|
||||
LOG.error("Failed to push " + responseString);
|
||||
return 0;
|
||||
}
|
||||
final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
|
||||
final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons(true).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,7 +21,8 @@ import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface EduPluginConfigurator {
|
||||
String EP_NAME = "Edu.pluginConfigurator";
|
||||
@@ -64,7 +65,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 +129,7 @@ public interface EduPluginConfigurator {
|
||||
@Nullable String moduleDir) {
|
||||
}
|
||||
|
||||
default List<String> getBundledCoursePaths() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import com.intellij.openapi.startup.StartupManager;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileAdapter;
|
||||
import com.intellij.openapi.vfs.VirtualFileEvent;
|
||||
import com.intellij.openapi.vfs.VirtualFileListener;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
@@ -39,9 +39,9 @@ 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.courseFormat.CourseInfo;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.ui.StudyToolWindow;
|
||||
import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
|
||||
@@ -50,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 {
|
||||
@@ -80,39 +78,18 @@ public class StudyProjectComponent implements ProjectComponent {
|
||||
}
|
||||
|
||||
if (course != null && !course.isAdaptive() && !course.isUpToDate()) {
|
||||
final Notification notification =
|
||||
new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
|
||||
new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
|
||||
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
return execCancelable(() -> {
|
||||
updateCourse();
|
||||
return true;
|
||||
});
|
||||
}, "Updating Course", true, myProject);
|
||||
EduUtils.synchronize();
|
||||
course.setUpdated();
|
||||
|
||||
}
|
||||
});
|
||||
notification.notify(myProject);
|
||||
|
||||
updateAvailable(course);
|
||||
}
|
||||
|
||||
StudyUtils.registerStudyToolWindow(course, myProject);
|
||||
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().invokeLater(
|
||||
(DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction((DumbAwareRunnable)() -> {
|
||||
Course course1 = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
if (course1 != null) {
|
||||
if (course != null) {
|
||||
UISettings instance = UISettings.getInstance();
|
||||
instance.setHideToolStripes(false);
|
||||
instance.fireUISettingsChanged();
|
||||
registerShortcuts();
|
||||
EduUsagesCollector.projectTypeOpened(course1.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
|
||||
EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
|
||||
}
|
||||
})));
|
||||
|
||||
@@ -128,6 +105,29 @@ public class StudyProjectComponent implements ProjectComponent {
|
||||
});
|
||||
}
|
||||
|
||||
private void updateAvailable(Course course) {
|
||||
final Notification notification =
|
||||
new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
|
||||
new NotificationListener() {
|
||||
@Override
|
||||
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
|
||||
FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
|
||||
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
return execCancelable(() -> {
|
||||
updateCourse();
|
||||
return true;
|
||||
});
|
||||
}, "Updating Course", true, myProject);
|
||||
EduUtils.synchronize();
|
||||
course.setUpdated();
|
||||
|
||||
}
|
||||
});
|
||||
notification.notify(myProject);
|
||||
}
|
||||
|
||||
private void registerShortcuts() {
|
||||
StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject);
|
||||
if (window != null) {
|
||||
@@ -148,18 +148,9 @@ public class StudyProjectComponent implements ProjectComponent {
|
||||
|
||||
private void updateCourse() {
|
||||
final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
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 (currentCourse == null || !(currentCourse instanceof RemoteCourse)) return;
|
||||
final Course course = EduStepicConnector.getCourse(myProject, (RemoteCourse)currentCourse);
|
||||
if (course == null) return;
|
||||
flushCourse(course);
|
||||
course.initCourse(false);
|
||||
|
||||
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById());
|
||||
@@ -171,19 +162,19 @@ public class StudyProjectComponent implements ProjectComponent {
|
||||
final ArrayList<Lesson> updatedLessons = new ArrayList<>();
|
||||
|
||||
int lessonIndex = 0;
|
||||
for (Lesson lesson : course.getLessons()) {
|
||||
for (Lesson lesson : course.getLessons(true)) {
|
||||
lessonIndex += 1;
|
||||
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);
|
||||
@@ -211,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);
|
||||
}
|
||||
@@ -232,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()) {
|
||||
@@ -333,7 +312,7 @@ public class StudyProjectComponent implements ProjectComponent {
|
||||
return module.getComponent(StudyProjectComponent.class);
|
||||
}
|
||||
|
||||
private class FileCreatedByUserListener extends VirtualFileAdapter {
|
||||
private class FileCreatedByUserListener implements VirtualFileListener {
|
||||
@Override
|
||||
public void fileCreated(@NotNull VirtualFileEvent event) {
|
||||
if (myProject.isDisposed()) return;
|
||||
|
||||
+15
-74
@@ -5,17 +5,13 @@ import com.google.gson.reflect.TypeToken;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.EditorFactory;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
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.VirtualFile;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.hash.HashMap;
|
||||
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.tasks.*;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
@@ -26,7 +22,6 @@ import org.jdom.Element;
|
||||
import org.jdom.output.XMLOutputter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -62,6 +57,7 @@ public class StudySerializationUtils {
|
||||
public static class Xml {
|
||||
public final static String COURSE_ELEMENT = "courseElement";
|
||||
public final static String MAIN_ELEMENT = "StudyTaskManager";
|
||||
public final static String REMOTE_COURSE = "RemoteCourse";
|
||||
public static final String MAP = "map";
|
||||
public static final String KEY = "key";
|
||||
public static final String VALUE = "value";
|
||||
@@ -262,14 +258,17 @@ public class StudySerializationUtils {
|
||||
final Element adaptive = getChildWithName(courseElement, ADAPTIVE);
|
||||
for (Element lesson : getChildList(courseElement, LESSONS)) {
|
||||
for (Element task : getChildList(lesson, TASK_LIST)) {
|
||||
final Element lastSubtaskIndex = getChildWithName(task, LAST_SUBTASK_INDEX);
|
||||
final Element theoryTask = getChildWithName(task, THEORY_TAG);
|
||||
final Element adaptiveParams = getChildWithName(task, ADAPTIVE_TASK_PARAMETERS);
|
||||
final boolean hasAdaptiveParams = !adaptiveParams.getChildren().isEmpty();
|
||||
if (Integer.valueOf(lastSubtaskIndex.getAttributeValue(VALUE)) != 0) {
|
||||
final Element lastSubtaskIndex = getChildWithName(task, LAST_SUBTASK_INDEX, true); //could be broken by 3->4 migration
|
||||
final Element adaptiveParams = getChildWithName(task, ADAPTIVE_TASK_PARAMETERS, true);
|
||||
Element theoryTask = getChildWithName(task, THEORY_TAG, true);
|
||||
if (theoryTask == null && adaptiveParams != null) {
|
||||
theoryTask = getChildWithName(adaptiveParams, THEORY_TAG, true);
|
||||
}
|
||||
final boolean hasAdaptiveParams = adaptiveParams != null && !adaptiveParams.getChildren().isEmpty();
|
||||
if (lastSubtaskIndex != null && Integer.valueOf(lastSubtaskIndex.getAttributeValue(VALUE)) != 0) {
|
||||
task.setName(TASK_WITH_SUBTASKS);
|
||||
}
|
||||
else if (Boolean.valueOf(theoryTask.getAttributeValue(VALUE))) {
|
||||
else if (theoryTask != null && Boolean.valueOf(theoryTask.getAttributeValue(VALUE))) {
|
||||
task.setName(THEORY_TASK);
|
||||
}
|
||||
else if (hasAdaptiveParams) {
|
||||
@@ -455,61 +454,6 @@ public class StudySerializationUtils {
|
||||
private Json() {
|
||||
}
|
||||
|
||||
public static class CourseTypeAdapter implements JsonDeserializer<Course> {
|
||||
|
||||
private final File myCourseFile;
|
||||
|
||||
public CourseTypeAdapter(File courseFile) {
|
||||
myCourseFile = courseFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject courseObject = json.getAsJsonObject();
|
||||
JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
|
||||
for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
|
||||
JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
|
||||
JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
|
||||
for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
|
||||
JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
|
||||
for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
|
||||
String name = taskFile.getKey();
|
||||
String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
|
||||
VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
|
||||
if (resourceFile == null) {
|
||||
continue;
|
||||
}
|
||||
Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
|
||||
if (document == null) {
|
||||
continue;
|
||||
}
|
||||
JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
|
||||
JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
|
||||
for (JsonElement placeholder : placeholders) {
|
||||
convertToAbsoluteOffset(document, placeholder);
|
||||
if (placeholder.getAsJsonObject().getAsJsonObject(SUBTASK_INFOS) == null) {
|
||||
convertToSubtaskInfo(placeholder.getAsJsonObject());
|
||||
removeIndexFromSubtaskInfos(placeholder.getAsJsonObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new GsonBuilder().registerTypeAdapter(Task.class, new TaskDeserializer()).create().fromJson(json, Course.class);
|
||||
}
|
||||
|
||||
private static void convertToAbsoluteOffset(Document document, JsonElement placeholder) {
|
||||
JsonObject placeholderObject = placeholder.getAsJsonObject();
|
||||
if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
|
||||
return;
|
||||
}
|
||||
int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
|
||||
int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
|
||||
int offset = document.getLineStartOffset(line) + start;
|
||||
placeholderObject.addProperty(OFFSET, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StepicStepOptionsAdapter implements JsonDeserializer<StepicWrappers.StepOptions> {
|
||||
@Override
|
||||
public StepicWrappers.StepOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||
@@ -537,7 +481,7 @@ public class StudySerializationUtils {
|
||||
return stepOptions;
|
||||
}
|
||||
|
||||
private JsonObject convertToThirdVersion(JsonObject stepOptionsJson) {
|
||||
private static JsonObject convertToThirdVersion(JsonObject stepOptionsJson) {
|
||||
if (!stepOptionsJson.has(LAST_SUBTASK)) return stepOptionsJson;
|
||||
final int lastSubtaskIndex = stepOptionsJson.get(LAST_SUBTASK).getAsInt();
|
||||
if (lastSubtaskIndex == 0) return stepOptionsJson;
|
||||
@@ -549,7 +493,7 @@ public class StudySerializationUtils {
|
||||
}
|
||||
}
|
||||
final JsonArray descriptions = stepOptionsJson.getAsJsonArray(TEXTS);
|
||||
if (descriptions.size() > 0) {
|
||||
if (descriptions != null && descriptions.size() > 0) {
|
||||
final JsonObject fileWrapper = descriptions.get(0).getAsJsonObject();
|
||||
if (fileWrapper.has(NAME)) {
|
||||
replaceWithSubtask(fileWrapper);
|
||||
@@ -558,7 +502,7 @@ public class StudySerializationUtils {
|
||||
return stepOptionsJson;
|
||||
}
|
||||
|
||||
private void replaceWithSubtask(JsonObject fileWrapper) {
|
||||
private static void replaceWithSubtask(JsonObject fileWrapper) {
|
||||
final String file = fileWrapper.get(NAME).getAsString();
|
||||
final String extension = FileUtilRt.getExtension(file);
|
||||
final String name = FileUtil.getNameWithoutExtension(file);
|
||||
@@ -673,7 +617,7 @@ public class StudySerializationUtils {
|
||||
subtaskInfo.addProperty(POSSIBLE_ANSWER, placeholderObject.getAsJsonPrimitive(POSSIBLE_ANSWER).getAsString());
|
||||
}
|
||||
|
||||
public static class TaskSerializer implements JsonSerializer<Task> {
|
||||
public static class TaskAdapter implements JsonSerializer<Task>, JsonDeserializer<Task> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Task src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
@@ -683,9 +627,6 @@ public class StudySerializationUtils {
|
||||
task.add(TASK_TYPE, new JsonPrimitive(src.getTaskType()));
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TaskDeserializer implements JsonDeserializer<Task> {
|
||||
|
||||
@Override
|
||||
public Task deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
@@ -705,7 +646,7 @@ public class StudySerializationUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.warn("No task type found in json " + json.getAsString());
|
||||
LOG.warn("No task type found in json " + json.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.jetbrains.edu.learning;
|
||||
|
||||
import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.components.State;
|
||||
@@ -7,25 +11,31 @@ import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.startup.StartupManager;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.containers.hash.HashMap;
|
||||
import com.intellij.util.xmlb.XmlSerializer;
|
||||
import com.intellij.util.xmlb.annotations.Transient;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
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;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.REMOTE_COURSE;
|
||||
|
||||
/**
|
||||
* Implementation of class which contains all the information
|
||||
* about study in context of current project
|
||||
@@ -39,7 +49,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;
|
||||
@@ -118,10 +127,32 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
if (myCourse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return serialize();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Element serialize() {
|
||||
Element el = new Element("taskManager");
|
||||
Element courseElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT);
|
||||
XmlSerializer.serializeInto(this, courseElement);
|
||||
el.addContent(courseElement);
|
||||
Element taskManagerElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT);
|
||||
XmlSerializer.serializeInto(this, taskManagerElement);
|
||||
|
||||
if (myCourse instanceof RemoteCourse) {
|
||||
try {
|
||||
Element course = new Element(REMOTE_COURSE);
|
||||
//noinspection RedundantCast
|
||||
XmlSerializer.serializeInto((RemoteCourse)myCourse, course);
|
||||
|
||||
final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE);
|
||||
xmlCourse.removeContent();
|
||||
xmlCourse.addContent(course);
|
||||
}
|
||||
catch (StudySerializationUtils.StudyUnrecognizedFormatException e) {
|
||||
LOG.error("Failed to serialize remote course");
|
||||
}
|
||||
}
|
||||
|
||||
el.addContent(taskManagerElement);
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -142,20 +173,15 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
state = StudySerializationUtils.Xml.convertToForthVersion(state);
|
||||
case 4:
|
||||
state = StudySerializationUtils.Xml.convertToFifthVersion(state);
|
||||
updateTestHelper();
|
||||
//uncomment for future versions
|
||||
//case 5:
|
||||
// state = StudySerializationUtils.Xml.convertToSixthVersion(state, myProject);
|
||||
}
|
||||
XmlSerializer.deserializeInto(this, state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT));
|
||||
deserialize(state);
|
||||
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) {
|
||||
@@ -163,18 +189,44 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTestHelper() {
|
||||
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
final VirtualFile testHelper = myProject.getBaseDir().findChild(EduNames.TEST_HELPER);
|
||||
if (testHelper != null) {
|
||||
StudyUtils.deleteFile(testHelper);
|
||||
}
|
||||
final FileTemplate template =
|
||||
FileTemplateManager.getInstance(myProject).getInternalTemplate(FileUtil.getNameWithoutExtension(EduNames.TEST_HELPER));
|
||||
try {
|
||||
final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir());
|
||||
if (projectDir != null) {
|
||||
FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn("Failed to create new test helper");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void deserialize(Element state) throws StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
final Element taskManagerElement = state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT);
|
||||
if (taskManagerElement == null)
|
||||
throw new StudySerializationUtils.StudyUnrecognizedFormatException();
|
||||
XmlSerializer.deserializeInto(this, taskManagerElement);
|
||||
final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE);
|
||||
final Element remoteCourseElement = xmlCourse.getChild(REMOTE_COURSE);
|
||||
if (remoteCourseElement != null) {
|
||||
final RemoteCourse remoteCourse = new RemoteCourse();
|
||||
XmlSerializer.deserializeInto(remoteCourse, remoteCourseElement);
|
||||
myCourse = remoteCourse;
|
||||
}
|
||||
}
|
||||
|
||||
public static StudyTaskManager getInstance(@NotNull final Project project) {
|
||||
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();
|
||||
@@ -497,7 +454,6 @@ public class StudyUtils {
|
||||
String text = task.getTaskDescription() != null ? task.getTaskDescription() : getTaskTextByTaskName(task, taskDirectory);
|
||||
|
||||
if (text == null) return null;
|
||||
text = convertToHtml(text);
|
||||
if (course.isAdaptive()) text = wrapAdaptiveCourseText(task, text);
|
||||
|
||||
return wrapTextToDisplayLatex(text);
|
||||
@@ -537,20 +493,33 @@ public class StudyUtils {
|
||||
@Nullable
|
||||
private static String getTaskTextByTaskName(@NotNull Task task, @Nullable VirtualFile taskDirectory) {
|
||||
if (taskDirectory == null) return null;
|
||||
final String taskFileNameMd = constructTaskTextFilename(task, EduNames.TASK_MD);
|
||||
final String taskFileNameHtml = constructTaskTextFilename(task, EduNames.TASK_HTML);
|
||||
|
||||
VirtualFile taskTextFile = ObjectUtils.chooseNotNull(taskDirectory.findChild(taskFileNameMd), taskDirectory.findChild(taskFileNameHtml));
|
||||
|
||||
if (taskTextFile == null) {
|
||||
VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC);
|
||||
if (srcDir != null) {
|
||||
taskTextFile = ObjectUtils.chooseNotNull(srcDir.findChild(taskFileNameHtml), srcDir.findChild(taskFileNameMd));
|
||||
}
|
||||
String textFromHtmlFile = getTextByTaskFileFormat(task, taskDirectory, EduNames.TASK_HTML);
|
||||
if (textFromHtmlFile != null) {
|
||||
return textFromHtmlFile;
|
||||
}
|
||||
|
||||
String taskTextFromMd = getTextByTaskFileFormat(task, taskDirectory, EduNames.TASK_HTML);
|
||||
return convertToHtml(taskTextFromMd);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getTextByTaskFileFormat(@NotNull Task task, @NotNull VirtualFile taskDirectory, @NotNull String taskTextFileName) {
|
||||
String textFilename = constructTaskTextFilename(task, taskTextFileName);
|
||||
VirtualFile taskTextFile = taskDirectory.findChild(taskTextFileName);
|
||||
|
||||
if (taskTextFile != null) {
|
||||
return String.valueOf(LoadTextUtil.loadText(taskTextFile));
|
||||
}
|
||||
|
||||
VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC);
|
||||
if (srcDir != null) {
|
||||
VirtualFile taskTextSrcFile = srcDir.findChild(textFilename);
|
||||
if (taskTextSrcFile != null) {
|
||||
return String.valueOf(LoadTextUtil.loadText(taskTextSrcFile));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -611,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");
|
||||
@@ -713,7 +665,9 @@ public class StudyUtils {
|
||||
return task;
|
||||
}
|
||||
|
||||
private static String convertToHtml(@NotNull final String content) {
|
||||
@Nullable
|
||||
private static String convertToHtml(@Nullable final String content) {
|
||||
if (content == null) return null;
|
||||
ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
|
||||
MarkdownUtil.replaceHeaders(lines);
|
||||
MarkdownUtil.replaceCodeBlock(lines);
|
||||
@@ -866,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) {
|
||||
|
||||
@@ -8,35 +8,32 @@ import com.intellij.util.xmlb.XmlSerializer;
|
||||
import com.intellij.util.xmlb.annotations.Transient;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.core.EduUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.StepicUser;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Collections;
|
||||
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;
|
||||
@Expose @SerializedName("language") private String myLanguage = "Python";
|
||||
public static final Course INVALID_COURSE = new Course();
|
||||
|
||||
@Expose protected List<Lesson> lessons = new ArrayList<>();
|
||||
transient private List<StepicUser> authors = new ArrayList<>();
|
||||
@Expose @SerializedName("summary") private String description;
|
||||
@Expose @SerializedName("title") private String name;
|
||||
|
||||
transient private String myLanguage = "Python";
|
||||
|
||||
//this field is used to distinguish ordinary and CheckIO projects,
|
||||
//"PyCharm" is used here for historical reasons
|
||||
private String courseType = EduNames.PYCHARM;
|
||||
private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
|
||||
protected 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 +45,18 @@ public class Course {
|
||||
}
|
||||
|
||||
public List<Lesson> getLessons() {
|
||||
return lessons;
|
||||
return getLessons(false);
|
||||
}
|
||||
|
||||
public List<Lesson> getLessons(boolean withAdditional) {
|
||||
return withAdditional ? 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,9 +65,12 @@ 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();
|
||||
if (!EduUtils.indexIsValid(lessonIndex, lessons)) {
|
||||
return null;
|
||||
}
|
||||
@@ -118,14 +124,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;
|
||||
}
|
||||
@@ -135,35 +133,10 @@ public class Course {
|
||||
}
|
||||
|
||||
public boolean isUpToDate() {
|
||||
if (id == 0) return true;
|
||||
if (!EduNames.STUDY.equals(courseMode)) return true;
|
||||
final Date date = EduStepicConnector.getCourseUpdateDate(id);
|
||||
if (date == null) return true;
|
||||
if (myUpdateDate == null) return true;
|
||||
if (date.after(myUpdateDate)) return false;
|
||||
for (Lesson lesson : lessons) {
|
||||
if (!lesson.isUpToDate()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setUpdated() {
|
||||
setUpdateDate(EduStepicConnector.getCourseUpdateDate(id));
|
||||
for (Lesson lesson : lessons) {
|
||||
lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId()));
|
||||
for (Task task : lesson.getTaskList()) {
|
||||
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpdateDate(Date date) {
|
||||
myUpdateDate = date;
|
||||
}
|
||||
|
||||
public Date getUpdateDate() {
|
||||
return myUpdateDate;
|
||||
}
|
||||
public void setUpdated() {}
|
||||
|
||||
public Language getLanguageById() {
|
||||
return Language.findLanguageByID(getLanguageID());
|
||||
@@ -208,22 +181,6 @@ public class Course {
|
||||
this.courseType = courseType;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return isAdaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
isAdaptive = adaptive;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCourseMode() {
|
||||
return courseMode;
|
||||
}
|
||||
@@ -234,8 +191,21 @@ public class Course {
|
||||
|
||||
public Course copy() {
|
||||
Element element = XmlSerializer.serialize(this);
|
||||
Course copy = XmlSerializer.deserialize(element, Course.class);
|
||||
Course copy = XmlSerializer.deserialize(element, getClass());
|
||||
copy.initCourse(true);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sortLessons() {
|
||||
Collections.sort(lessons, EduUtils.INDEX_COMPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
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;
|
||||
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
|
||||
* and when project is being created
|
||||
*/
|
||||
public class CourseInfo {
|
||||
public static final CourseInfo INVALID_COURSE = new CourseInfo();
|
||||
|
||||
@SerializedName("title") private String myName;
|
||||
int id;
|
||||
boolean isAdaptive;
|
||||
@SerializedName("is_public") boolean isPublic;
|
||||
List<Integer> sections;
|
||||
List<Integer> instructors = new ArrayList<>();
|
||||
|
||||
List<StepicUser> myAuthors = new ArrayList<>();
|
||||
@SerializedName("summary") private String myDescription;
|
||||
@SerializedName("course_format") private String myType = "pycharm" + EduStepicConnector.CURRENT_VERSION + " Python"; //course type in format "pycharm<version> <language>"
|
||||
@Nullable private String username;
|
||||
|
||||
@SerializedName("update_date") private Date updateDate;
|
||||
@SerializedName("is_idea_compatible") private boolean isCompatible = true;
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<StepicUser> getAuthors() {
|
||||
return myAuthors;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CourseInfo that = (CourseInfo)o;
|
||||
final String thatName = that.getName();
|
||||
final String thatDescription = that.getDescription();
|
||||
if (thatName == null && getName() == null && thatDescription == null &&
|
||||
getDescription() == null) {
|
||||
return true;
|
||||
}
|
||||
if (thatName == null || thatDescription == null) {
|
||||
return false;
|
||||
}
|
||||
return thatName.equals(getName())
|
||||
&& thatDescription.equals(myDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = getName() != null ? getName().hashCode() : 0;
|
||||
result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(@Nullable String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
myName = name;
|
||||
}
|
||||
|
||||
public void setAuthors(List<StepicUser> authors) {
|
||||
myAuthors = authors;
|
||||
for (StepicUser author : authors) {
|
||||
if (author.getId() > 0) {
|
||||
instructors.add(author.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addAuthor(StepicUser author) {
|
||||
if (myAuthors == null) {
|
||||
myAuthors = new ArrayList<>();
|
||||
}
|
||||
myAuthors.add(author);
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
myDescription = description;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
myType = type;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return isAdaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
isAdaptive = adaptive;
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public void setPublic(boolean aPublic) {
|
||||
isPublic = aPublic;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getUpdateDate() {
|
||||
return updateDate;
|
||||
}
|
||||
|
||||
public void setUpdateDate(Date updateDate) {
|
||||
this.updateDate = updateDate;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public List<Integer> getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
public boolean isCompatible() {
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
public void setCompatible(boolean compatible) {
|
||||
isCompatible = compatible;
|
||||
}
|
||||
|
||||
public List<Integer> getInstructors() {
|
||||
return instructors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.jetbrains.edu.learning.courseFormat;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.courseFormat.tasks.Task;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class RemoteCourse extends Course {
|
||||
//course type in format "pycharm<version> <language>"
|
||||
@SerializedName("course_format") private String myType = "pycharm" + EduStepicConnector.CURRENT_VERSION + " Python";
|
||||
@SerializedName("is_idea_compatible") private boolean isCompatible = true;
|
||||
List<Integer> sections;
|
||||
List<Integer> instructors = new ArrayList<>();
|
||||
@Expose private int id;
|
||||
@Expose @SerializedName("update_date") private Date myUpdateDate;
|
||||
@Expose private boolean isAdaptive = false;
|
||||
@SerializedName("is_public") boolean isPublic;
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public List<Integer> getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
public void setSections(List<Integer> sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
|
||||
public void setInstructors(List<Integer> instructors) {
|
||||
this.instructors = instructors;
|
||||
}
|
||||
|
||||
public List<Integer> getInstructors() {
|
||||
return instructors;
|
||||
}
|
||||
|
||||
|
||||
public boolean isUpToDate() {
|
||||
if (id == 0) return true;
|
||||
if (!EduNames.STUDY.equals(courseMode)) return true;
|
||||
final Date date = EduStepicConnector.getCourseUpdateDate(id);
|
||||
if (date == null) return true;
|
||||
if (myUpdateDate == null) return true;
|
||||
if (date.after(myUpdateDate)) return false;
|
||||
for (Lesson lesson : lessons) {
|
||||
if (!lesson.isUpToDate()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setUpdated() {
|
||||
setUpdateDate(EduStepicConnector.getCourseUpdateDate(id));
|
||||
for (Lesson lesson : lessons) {
|
||||
lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId()));
|
||||
for (Task task : lesson.getTaskList()) {
|
||||
task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpdateDate(Date date) {
|
||||
myUpdateDate = date;
|
||||
}
|
||||
|
||||
public Date getUpdateDate() {
|
||||
return myUpdateDate;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return isAdaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
isAdaptive = adaptive;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void copyCourseParameters(RemoteCourse course) {
|
||||
setName(course.getName());
|
||||
|
||||
setUpdateDate(course.getUpdateDate());
|
||||
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -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;
|
||||
@@ -105,10 +112,6 @@ public class Task implements StudyItem {
|
||||
return name != null ? taskFiles.get(name) : null;
|
||||
}
|
||||
|
||||
public boolean isTaskFile(@NotNull final String fileName) {
|
||||
return taskFiles.get(fileName) != null;
|
||||
}
|
||||
|
||||
public void addTaskFile(@NotNull final String name, int index) {
|
||||
TaskFile taskFile = new TaskFile();
|
||||
taskFile.setIndex(index);
|
||||
@@ -226,7 +229,7 @@ public class Task implements StudyItem {
|
||||
|
||||
public Task copy() {
|
||||
Element element = XmlSerializer.serialize(this);
|
||||
Task copy = XmlSerializer.deserialize(element, Task.class);
|
||||
Task copy = XmlSerializer.deserialize(element, getClass());
|
||||
copy.initTask(null, true);
|
||||
return copy;
|
||||
}
|
||||
|
||||
+96
-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,92 @@ 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()));
|
||||
createTaskContent(task, taskDir);
|
||||
}
|
||||
|
||||
public static void createTaskContent(@NotNull Task task, VirtualFile taskDir) throws IOException {
|
||||
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);
|
||||
}
|
||||
|
||||
public 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());
|
||||
}
|
||||
}
|
||||
|
||||
public 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+68
-471
@@ -1,66 +1,51 @@
|
||||
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.RemoteCourse;
|
||||
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> myCourses = new ArrayList<>();
|
||||
private List<Integer> myEnrolledCoursesIds = new ArrayList<>();
|
||||
protected CourseInfo mySelectedCourseInfo;
|
||||
protected Course mySelectedCourse;
|
||||
|
||||
public void setCourses(List<CourseInfo> courses) {
|
||||
public void setCourses(List<Course> courses) {
|
||||
myCourses = courses;
|
||||
}
|
||||
|
||||
@@ -78,12 +63,12 @@ public class StudyProjectGenerator {
|
||||
return myEnrolledCoursesIds;
|
||||
}
|
||||
|
||||
public void setSelectedCourse(@NotNull final CourseInfo courseName) {
|
||||
mySelectedCourseInfo = courseName;
|
||||
public void setSelectedCourse(@NotNull final Course course) {
|
||||
mySelectedCourse = course;
|
||||
}
|
||||
|
||||
public CourseInfo getSelectedCourseInfo() {
|
||||
return mySelectedCourseInfo;
|
||||
public Course getSelectedCourse() {
|
||||
return mySelectedCourse;
|
||||
}
|
||||
|
||||
public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
|
||||
@@ -97,50 +82,30 @@ 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()) {
|
||||
return course;
|
||||
}
|
||||
return getCourseFromStepic(project);
|
||||
if (mySelectedCourse instanceof RemoteCourse) {
|
||||
return getCourseFromStepic(project, (RemoteCourse)mySelectedCourse);
|
||||
}
|
||||
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);
|
||||
mySelectedCourse.initCourse(false);
|
||||
return mySelectedCourse;
|
||||
}
|
||||
|
||||
private Course getCourseFromStepic(@NotNull Project project) {
|
||||
private static RemoteCourse getCourseFromStepic(@NotNull Project project, RemoteCourse selectedCourse) {
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
return execCancelable(() -> {
|
||||
final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
|
||||
final RemoteCourse course = EduStepicConnector.getCourse(project, selectedCourse);
|
||||
if (StudyUtils.isCourseValid(course)) {
|
||||
flushCourse(course);
|
||||
course.initCourse(false);
|
||||
}
|
||||
return course;
|
||||
@@ -148,281 +113,26 @@ 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());
|
||||
}
|
||||
|
||||
private 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.isEmpty()) {
|
||||
public List<Course> getCourses(boolean force) {
|
||||
if (force) {
|
||||
myCourses = execCancelable(() -> EduStepicConnector.getCourses(StudySettings.getInstance().getUser()));
|
||||
flushCache(myCourses);
|
||||
}
|
||||
if (myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(CourseInfo.INVALID_COURSE))) {
|
||||
myCourses = Collections.singletonList(getBundledIntro());
|
||||
if (myCourses == null || myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(Course.INVALID_COURSE))) {
|
||||
myCourses = getBundledCourses();
|
||||
}
|
||||
sortCourses(myCourses);
|
||||
return myCourses;
|
||||
}
|
||||
|
||||
public void sortCourses(List<CourseInfo> result) {
|
||||
public void sortCourses(List<Course> result) {
|
||||
// sort courses so as to have non-adaptive courses in the beginning of the list
|
||||
Collections.sort(result, (c1, c2) -> {
|
||||
if (mySelectedCourseInfo != null) {
|
||||
if (mySelectedCourseInfo.equals(c1)) {
|
||||
if (mySelectedCourse != null) {
|
||||
if (mySelectedCourse.equals(c1)) {
|
||||
return -1;
|
||||
}
|
||||
if (mySelectedCourseInfo.equals(c2)) {
|
||||
if (mySelectedCourse.equals(c2)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -434,7 +144,7 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
|
||||
public List<Course> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
|
||||
try {
|
||||
return ProgressManager.getInstance()
|
||||
.runProcessWithProgressSynchronously(() -> {
|
||||
@@ -443,7 +153,7 @@ public class StudyProjectGenerator {
|
||||
}, progressTitle, true, project);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
return Collections.singletonList(CourseInfo.INVALID_COURSE);
|
||||
return Collections.singletonList(Course.INVALID_COURSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,78 +171,41 @@ public class StudyProjectGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
public static CourseInfo getBundledIntro() {
|
||||
final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
|
||||
if (introCourse.exists()) {
|
||||
return getCourseInfo(introCourse);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@Nullable
|
||||
public List<Course> getBundledCourses() {
|
||||
final ArrayList<Course> courses = new ArrayList<>();
|
||||
final LanguageExtensionPoint<EduPluginConfigurator>[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null);
|
||||
for (LanguageExtensionPoint<EduPluginConfigurator> extension : extensions) {
|
||||
final EduPluginConfigurator configurator = extension.getInstance();
|
||||
final List<String> paths = configurator.getBundledCoursePaths();
|
||||
for (String path : paths) {
|
||||
courses.add(getCourse(path));
|
||||
}
|
||||
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);
|
||||
public Course addLocalCourse(String zipFilePath) {
|
||||
final Course courseInfo = getCourse(zipFilePath);
|
||||
if (courseInfo != null) {
|
||||
myCourses.add(0, courseInfo);
|
||||
}
|
||||
return courseInfo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Course getCourse(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();
|
||||
}
|
||||
return courseName;
|
||||
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.TaskAdapter())
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.create();
|
||||
return gson.fromJson(jsonText, Course.class);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Failed to unzip course archive");
|
||||
@@ -540,80 +213,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());
|
||||
}
|
||||
}
|
||||
|
||||
+4
-7
@@ -14,7 +14,6 @@ import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.jetbrains.edu.learning.EduPluginConfigurator;
|
||||
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.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.intellij.generation.EduProjectGenerator;
|
||||
import org.jdom.JDOMException;
|
||||
@@ -27,7 +26,7 @@ import java.io.IOException;
|
||||
class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
|
||||
private static final Logger LOG = Logger.getInstance(EduCustomCourseModuleBuilder.class);
|
||||
private EduProjectGenerator myGenerator = new EduProjectGenerator();
|
||||
private CourseInfo mySelectedCourse;
|
||||
private Course mySelectedCourse;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -36,8 +35,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
|
||||
if (module == null) {
|
||||
return null;
|
||||
}
|
||||
String type = mySelectedCourse.getType();
|
||||
String languageName = type.substring("pycharm ".length());
|
||||
String languageName = mySelectedCourse.getLanguageID();
|
||||
Language language = Language.findLanguageByID(languageName);
|
||||
if (language == null) {
|
||||
return module;
|
||||
@@ -87,7 +85,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
|
||||
return ProjectWizardStepFactory.getInstance().createJavaSettingsStep(settingsStep, this, Conditions.alwaysTrue());
|
||||
}
|
||||
|
||||
void setSelectedCourse(CourseInfo selectedCourse) {
|
||||
void setSelectedCourse(Course selectedCourse) {
|
||||
mySelectedCourse = selectedCourse;
|
||||
}
|
||||
|
||||
@@ -97,8 +95,7 @@ class EduCustomCourseModuleBuilder extends JavaModuleBuilder {
|
||||
throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException {
|
||||
Module baseModule = super.createModule(moduleModel);
|
||||
if (mySelectedCourse != null) {
|
||||
String type = mySelectedCourse.getType();
|
||||
String languageName = type.substring("pycharm ".length());
|
||||
String languageName = mySelectedCourse.getLanguageID();
|
||||
Language language = Language.findLanguageByID(languageName);
|
||||
if (language != null) {
|
||||
EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language);
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@ package com.jetbrains.edu.learning.intellij.localCourses;
|
||||
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
|
||||
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -25,7 +25,7 @@ public class EduLocalCoursePanel {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
String coursePath = myCourseArchivePath.getText();
|
||||
CourseInfo courseInfo = generator.addLocalCourse(coursePath);
|
||||
Course courseInfo = generator.addLocalCourse(coursePath);
|
||||
if (courseInfo != null) {
|
||||
myBuilder.setSelectedCourse(courseInfo);
|
||||
myGenerator.setSelectedCourse(courseInfo);
|
||||
@@ -45,7 +45,7 @@ public class EduLocalCoursePanel {
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
myStudyPanel = new StudyNewProjectPanel(myGenerator);
|
||||
myStudyPanel = new StudyNewProjectPanel(myGenerator, true);
|
||||
myInfoPanel = myStudyPanel.getInfoPanel();
|
||||
}
|
||||
}
|
||||
|
||||
+19
-7
@@ -41,7 +41,15 @@ public class StudyNavigator {
|
||||
if (nextLesson == null) {
|
||||
return null;
|
||||
}
|
||||
return StudyUtils.getFirst(nextLesson.getTaskList());
|
||||
List<Task> nextLessonTaskList = nextLesson.getTaskList();
|
||||
while (nextLessonTaskList.isEmpty()) {
|
||||
nextLesson = nextLesson(nextLesson);
|
||||
if (nextLesson == null) {
|
||||
return null;
|
||||
}
|
||||
nextLessonTaskList = nextLesson.getTaskList();
|
||||
}
|
||||
return StudyUtils.getFirst(nextLessonTaskList);
|
||||
}
|
||||
|
||||
public static Task previousTask(@NotNull final Task task) {
|
||||
@@ -55,7 +63,15 @@ public class StudyNavigator {
|
||||
return null;
|
||||
}
|
||||
//getting last task in previous lesson
|
||||
return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1);
|
||||
List<Task> prevLessonTaskList = prevLesson.getTaskList();
|
||||
while (prevLessonTaskList.isEmpty()) {
|
||||
prevLesson = previousLesson(prevLesson);
|
||||
if (prevLesson == null) {
|
||||
return null;
|
||||
}
|
||||
prevLessonTaskList = prevLesson.getTaskList();
|
||||
}
|
||||
return prevLessonTaskList.get(prevLessonTaskList.size() - 1);
|
||||
}
|
||||
|
||||
public static Lesson nextLesson(@NotNull final Lesson lesson) {
|
||||
@@ -64,11 +80,7 @@ public class StudyNavigator {
|
||||
if (nextLessonIndex >= lessons.size()) {
|
||||
return null;
|
||||
}
|
||||
final Lesson nextLesson = lessons.get(nextLessonIndex);
|
||||
if (EduNames.PYCHARM_ADDITIONAL.equals(nextLesson.getName())) {
|
||||
return null;
|
||||
}
|
||||
return nextLesson;
|
||||
return lessons.get(nextLessonIndex);
|
||||
}
|
||||
|
||||
public static Lesson previousLesson(@NotNull final Lesson lesson) {
|
||||
|
||||
-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);
|
||||
|
||||
+159
-141
@@ -7,15 +7,12 @@ 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.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -31,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;
|
||||
@@ -49,15 +45,11 @@ 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep;
|
||||
@@ -72,11 +64,12 @@ public class EduAdaptiveStepicConnector {
|
||||
private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
|
||||
private static final int CONNECTION_TIMEOUT = 60 * 1000;
|
||||
private static final String CODE_TASK_TYPE = "code";
|
||||
private static final String CODE_TASK_NAME = "code.py";
|
||||
private static final String CHOICE_TYPE_TEXT = "choice";
|
||||
private static final String TEXT_STEP_TYPE = "text";
|
||||
|
||||
@Nullable
|
||||
public static Task getNextRecommendation(@NotNull Project project, @NotNull Course course) {
|
||||
public static Task getNextRecommendation(@NotNull Project project, @NotNull RemoteCourse course) {
|
||||
try {
|
||||
final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient();
|
||||
if (client == null) {
|
||||
@@ -134,16 +127,8 @@ public class EduAdaptiveStepicConnector {
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Connection problems, Please, try again"));
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
@@ -209,8 +194,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("code.py", taskFile);
|
||||
taskFile.name = CODE_TASK_NAME;
|
||||
task.taskFiles.put(CODE_TASK_NAME, taskFile);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -267,9 +252,7 @@ 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)));
|
||||
@@ -293,133 +276,171 @@ 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 reaction) {
|
||||
public static void addNextRecommendedTask(@NotNull Project project, @NotNull ProgressIndicator indicator, int reactionToPost) {
|
||||
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 boolean recommendationReaction = postRecommendationReaction(String.valueOf(editor.getTaskFile().getTask().getLesson().getId()),
|
||||
String.valueOf(user.getId()), reaction);
|
||||
if (recommendationReaction) {
|
||||
indicator.checkCanceled();
|
||||
final Task task = getNextRecommendation(project, course);
|
||||
if (course == null || editor == null || editor.getTaskFile() == null || !(course instanceof RemoteCourse)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
final Lesson adaptive = course.getLessons().get(0);
|
||||
task.initTask(adaptive, false);
|
||||
final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
|
||||
final String lessonName = EduNames.LESSON + String.valueOf(adaptive.getIndex());
|
||||
if (reaction == TOO_HARD_RECOMMENDATION_REACTION || reaction == TOO_BORING_RECOMMENDATION_REACTION) {
|
||||
task.setLesson(unsolvedTask.getLesson());
|
||||
task.setIndex(unsolvedTask.getIndex());
|
||||
adaptive.getTaskList().set(adaptive.getTaskList().size() - 1, task);
|
||||
ApplicationManager.getApplication().invokeLater(()->StudyNavigator.navigateToTask(project, task));
|
||||
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 File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
|
||||
final String taskName = EduNames.TASK + String.valueOf(adaptive.getTaskList().size());
|
||||
final File taskDirectory = new File(lessonDirectory, taskName);
|
||||
StudyProjectGenerator.flushTask(task, taskDirectory);
|
||||
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
|
||||
final Lesson lesson = editor.getTaskFile().getTask().getLesson();
|
||||
final boolean reactionPosted = postRecommendationReaction(String.valueOf(lesson.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 (lessonDir != null) {
|
||||
createTestFiles(course, project, unsolvedTask, lessonDir);
|
||||
}
|
||||
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
|
||||
if (window != null) {
|
||||
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getTaskDescription()), unsolvedTask.getTaskDir(project), project);
|
||||
}
|
||||
StudyNavigator.navigateToTask(project, lessonName, taskName);
|
||||
}
|
||||
else {
|
||||
adaptive.addTask(task);
|
||||
task.setIndex(adaptive.getTaskList().size());
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
|
||||
indicator.checkCanceled();
|
||||
final Task task = getNextRecommendation(project, (RemoteCourse)course);
|
||||
if (task == null) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project,
|
||||
"Couldn't load a new recommendation"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (lessonDir != null) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
|
||||
final String taskName = EduNames.TASK + String.valueOf(task.getIndex());
|
||||
final File taskDir = new File(lessonDirectory, taskName);
|
||||
StudyProjectGenerator.flushTask(task, taskDir);
|
||||
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
|
||||
StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
|
||||
adaptive.initLesson(course, true);
|
||||
StudyNavigator.navigateToTask(project, lessonName, taskName);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
task.initTask(lesson, false);
|
||||
boolean replaceCurrentTask = reactionToPost == TOO_HARD_RECOMMENDATION_REACTION || reactionToPost == TOO_BORING_RECOMMENDATION_REACTION;
|
||||
if (replaceCurrentTask) {
|
||||
replaceCurrentTask(project, editor, task);
|
||||
}
|
||||
else {
|
||||
addAsNextTask(project, editor, task);
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
|
||||
ProjectView.getInstance(project).refresh();
|
||||
StudyNavigator.navigateToTask(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 lesson = editor.getTaskFile().getTask().getLesson();
|
||||
lesson.addTask(task);
|
||||
task.setIndex(lesson.getTaskList().size());
|
||||
lesson.initLesson(course, true);
|
||||
|
||||
final String lessonName = EduNames.LESSON + lesson.getIndex();
|
||||
createFilesForNewTask(project, task, lessonName);
|
||||
}
|
||||
|
||||
private static void createFilesForNewTask(@NotNull Project project,
|
||||
@NotNull Task task,
|
||||
@NotNull String lessonName) {
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
|
||||
if (lessonDir != null) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
StudyGenerator.createTask(task, lessonDir);
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
final Balloon balloon =
|
||||
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("Couldn't load a new recommendation", MessageType.ERROR, null)
|
||||
.createBalloon();
|
||||
StudyUtils.showCheckPopUp(project, balloon);
|
||||
});
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
|
||||
ProjectView.getInstance(project).refresh();
|
||||
});
|
||||
}
|
||||
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 lesson = editor.getTaskFile().getTask().getLesson();
|
||||
int taskIndex = lesson.getTaskList().size();
|
||||
|
||||
task.setLesson(lesson);
|
||||
task.setIndex(taskIndex);
|
||||
lesson.getTaskList().set(taskIndex - 1, task);
|
||||
|
||||
copyTaskFileParameters(editor, task);
|
||||
|
||||
final String lessonName = EduNames.LESSON + lesson.getIndex();
|
||||
updateProjectFiles(project, task, lessonName);
|
||||
setToolWindowText(project, task);
|
||||
}
|
||||
|
||||
private static void updateProjectFiles(@NotNull Project project, @NotNull Task task, @NotNull String lessonName) {
|
||||
final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
|
||||
if (lessonDir != null) {
|
||||
final VirtualFile taskResourceRoot = lessonDir.findChild(EduNames.TASK + task.getIndex());
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
removeOldProjectFiles(project, taskResourceRoot);
|
||||
StudyGenerator.createTestFiles(taskResourceRoot, task);
|
||||
StudyGenerator.createDescriptions(taskResourceRoot, task);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeOldProjectFiles(@NotNull Project project, @Nullable VirtualFile taskRecourseRoot) throws IOException {
|
||||
if (taskRecourseRoot == null) {
|
||||
LOG.warn("Failed to update files for a new recommendation: task directory is null");
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualFile[] children = VfsUtil.getChildren(taskRecourseRoot);
|
||||
for (VirtualFile file : children) {
|
||||
String name = file.getName();
|
||||
if (StudyUtils.isTestsFile(project, name) || StudyUtils.isTaskDescriptionFile(name)) {
|
||||
file.delete(taskRecourseRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createTestFiles(@NotNull Course course, @NotNull Project project,
|
||||
@NotNull Task unsolvedTask, @NotNull VirtualFile lessonDir) {
|
||||
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 updateEditorText(@NotNull StudyEditor editor, @NotNull TaskFile newTaskFile) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
try {
|
||||
final VirtualFile taskDir = VfsUtil
|
||||
.findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
|
||||
final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
|
||||
|
||||
if (taskDir != null) {
|
||||
final File newResourceRoot = new File(resourceRoot, taskDir.getName());
|
||||
File[] filesInTask = newResourceRoot.listFiles();
|
||||
if (filesInTask != null) {
|
||||
for (File file : filesInTask) {
|
||||
final String taskRelativePath = FileUtil.getRelativePath(taskDir.getPath(), file.getPath(), '/');
|
||||
if (taskRelativePath != null && StudyUtils.isTestsFile(project, taskDir.getName())) {
|
||||
final File resourceFile = new File(newResourceRoot, taskRelativePath);
|
||||
final File fileInProject = new File(taskDir.getCanonicalPath(), taskRelativePath);
|
||||
FileUtil.copy(resourceFile, fileInProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.warn("Task directory is null");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
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(CODE_TASK_NAME, currentTaskFile);
|
||||
}
|
||||
|
||||
private static void setToolWindowText(@NotNull Project project, @NotNull Task task) {
|
||||
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
|
||||
if (window != null) {
|
||||
window.setTaskText(StudyUtils.wrapTextToDisplayLatex(task.getTaskDescription()), task.getTaskDir(project), project);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Task getCodeTaskFromStep(@NotNull Project project,
|
||||
@NotNull StepicWrappers.Step step,
|
||||
@@ -468,11 +489,9 @@ public class EduAdaptiveStepicConnector {
|
||||
}
|
||||
}
|
||||
else {
|
||||
final TaskFile taskFile = new TaskFile();
|
||||
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("code.py", taskFile);
|
||||
String text = templateForTask == null ? "# write your answer here \n" : templateForTask;
|
||||
createMockTaskFile(task, text);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
@@ -533,7 +552,6 @@ public class EduAdaptiveStepicConnector {
|
||||
return answer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<Boolean, String> checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) {
|
||||
int attemptId = -1;
|
||||
try {
|
||||
@@ -693,11 +711,11 @@ public class EduAdaptiveStepicConnector {
|
||||
public static List<Integer> getEnrolledCoursesIds(@NotNull StepicUser stepicUser) {
|
||||
try {
|
||||
final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build();
|
||||
final List<CourseInfo> courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(),
|
||||
final List<RemoteCourse> courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(),
|
||||
StepicWrappers.CoursesContainer.class,
|
||||
stepicUser).courses;
|
||||
final ArrayList<Integer> ids = new ArrayList<>();
|
||||
for (CourseInfo course : courses) {
|
||||
for (RemoteCourse course : courses) {
|
||||
ids.add(course.getId());
|
||||
}
|
||||
return ids;
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URI;
|
||||
@@ -70,6 +71,7 @@ public class EduStepicClient {
|
||||
|
||||
static <T> T deserializeStepicResponse(Class<T> container, String responseString) {
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(StepicWrappers.StepOptions.class, new StudySerializationUtils.Json.StepicStepOptionsAdapter()).
|
||||
excludeFieldsWithModifiers(Modifier.TRANSIENT).
|
||||
setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").
|
||||
setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
return gson.fromJson(responseString, container);
|
||||
|
||||
+38
-34
@@ -11,10 +11,7 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.jetbrains.edu.learning.StudySettings;
|
||||
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.courseFormat.Lesson;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
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.http.HttpEntity;
|
||||
@@ -65,9 +62,9 @@ public class EduStepicConnector {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<CourseInfo> getCourses(@Nullable StepicUser user) {
|
||||
public static List<Course> getCourses(@Nullable StepicUser user) {
|
||||
try {
|
||||
List<CourseInfo> result = new ArrayList<>();
|
||||
List<Course> result = new ArrayList<>();
|
||||
int pageNumber = 1;
|
||||
while (addCoursesFromStepic(user, result, pageNumber)) {
|
||||
pageNumber += 1;
|
||||
@@ -77,13 +74,13 @@ public class EduStepicConnector {
|
||||
catch (IOException e) {
|
||||
LOG.warn("Cannot load course list " + e.getMessage());
|
||||
}
|
||||
return Collections.singletonList(CourseInfo.INVALID_COURSE);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static Date getCourseUpdateDate(final int courseId) {
|
||||
final String url = EduStepicNames.COURSES + "/" + courseId;
|
||||
try {
|
||||
final List<CourseInfo> courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
|
||||
final List<RemoteCourse> courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses;
|
||||
if (!courses.isEmpty()) {
|
||||
return courses.get(0).getUpdateDate();
|
||||
}
|
||||
@@ -125,7 +122,7 @@ public class EduStepicConnector {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean addCoursesFromStepic(@Nullable StepicUser user, List<CourseInfo> result, int pageNumber) throws IOException {
|
||||
private static boolean addCoursesFromStepic(@Nullable StepicUser user, List<Course> result, int pageNumber) throws IOException {
|
||||
final URI url;
|
||||
try {
|
||||
url = new URIBuilder(EduStepicNames.COURSES).addParameter("is_idea_compatible", "true").
|
||||
@@ -146,27 +143,28 @@ public class EduStepicConnector {
|
||||
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
|
||||
}
|
||||
|
||||
static void addAvailableCourses(List<CourseInfo> result, StepicWrappers.CoursesContainer coursesContainer) throws IOException {
|
||||
final List<CourseInfo> courseInfos = coursesContainer.courses;
|
||||
for (CourseInfo info : courseInfos) {
|
||||
static void addAvailableCourses(List<Course> result, StepicWrappers.CoursesContainer coursesContainer) throws IOException {
|
||||
final List<RemoteCourse> courses = coursesContainer.courses;
|
||||
for (RemoteCourse info : courses) {
|
||||
if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(info.getType())) continue;
|
||||
if (canBeOpened(info)) {
|
||||
final ArrayList<StepicUser> authors = new ArrayList<>();
|
||||
for (Integer instructor : info.getInstructors()) {
|
||||
final StepicUser author = EduStepicClient.getFromStepic(EduStepicNames.USERS + String.valueOf(instructor),
|
||||
StepicWrappers.AuthorWrapper.class).users.get(0);
|
||||
info.addAuthor(author);
|
||||
authors.add(author);
|
||||
}
|
||||
info.setAuthors(authors);
|
||||
|
||||
if (info.isAdaptive()) {
|
||||
info.setDescription("This is a Stepik Adaptive course.\n\n" + info.getDescription() + ADAPTIVE_NOTE);
|
||||
}
|
||||
|
||||
result.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean canBeOpened(CourseInfo courseInfo) {
|
||||
static boolean canBeOpened(RemoteCourse courseInfo) {
|
||||
if (courseInfo.isAdaptive()) {
|
||||
return true;
|
||||
}
|
||||
@@ -190,26 +188,19 @@ public class EduStepicConnector {
|
||||
}
|
||||
}
|
||||
|
||||
public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
|
||||
final Course course = new Course();
|
||||
course.setAuthors(info.getAuthors());
|
||||
course.setDescription(info.getDescription());
|
||||
course.setAdaptive(info.isAdaptive());
|
||||
course.setId(info.getId());
|
||||
course.setUpdateDate(getCourseUpdateDate(info.getId()));
|
||||
|
||||
if (!course.isAdaptive()) {
|
||||
String courseType = info.getType();
|
||||
course.setName(info.getName());
|
||||
public static RemoteCourse getCourse(@NotNull final Project project, @NotNull final RemoteCourse course) {
|
||||
final RemoteCourse remoteCourse = (RemoteCourse)course.copy();
|
||||
if (!remoteCourse.isAdaptive()) {
|
||||
String courseType = remoteCourse.getType();
|
||||
final int separator = courseType.indexOf(" ");
|
||||
assert separator != -1;
|
||||
final String language = courseType.substring(separator + 1);
|
||||
course.setLanguage(language);
|
||||
remoteCourse.setLanguage(language);
|
||||
try {
|
||||
for (Integer section : info.getSections()) {
|
||||
course.addLessons(getLessons(section));
|
||||
for (Integer section : remoteCourse.getSections()) {
|
||||
remoteCourse.addLessons(getLessons(section));
|
||||
}
|
||||
return course;
|
||||
return remoteCourse;
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("IOException " + e.getMessage());
|
||||
@@ -218,14 +209,13 @@ public class EduStepicConnector {
|
||||
else {
|
||||
final Lesson lesson = new Lesson();
|
||||
lesson.setName("Adaptive");
|
||||
course.addLesson(lesson);
|
||||
course.setName(info.getName());
|
||||
remoteCourse.addLesson(lesson);
|
||||
//TODO: more specific name?
|
||||
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
|
||||
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, remoteCourse);
|
||||
if (recommendation != null) {
|
||||
lesson.addTask(recommendation);
|
||||
}
|
||||
return course;
|
||||
return remoteCourse;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -299,12 +289,26 @@ public class EduStepicConnector {
|
||||
task.taskFiles = new HashMap<>(); // TODO: it looks like we don't need taskFiles as map anymore
|
||||
if (block.options.files != null) {
|
||||
for (TaskFile taskFile : block.options.files) {
|
||||
addPlaceholdersTexts(taskFile);
|
||||
task.taskFiles.put(taskFile.name, taskFile);
|
||||
}
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private static void addPlaceholdersTexts(TaskFile file) {
|
||||
final String fileText = file.text;
|
||||
final List<AnswerPlaceholder> placeholders = file.getAnswerPlaceholders();
|
||||
for (AnswerPlaceholder placeholder : placeholders) {
|
||||
final AnswerPlaceholderSubtaskInfo info = placeholder.getActiveSubtaskInfo();
|
||||
final int offset = placeholder.getOffset();
|
||||
final int length = placeholder.getLength();
|
||||
if (fileText.length() > offset + length) {
|
||||
info.setPlaceholderText(fileText.substring(offset, offset+length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Task createTaskWithSubtasks(int lastSubtaskIndex) {
|
||||
TaskWithSubtasks task = new TaskWithSubtasks();
|
||||
|
||||
+19
-14
@@ -11,10 +11,12 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
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 com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class EduStepicUpdater {
|
||||
@@ -49,25 +51,29 @@ public class EduStepicUpdater {
|
||||
private static ActionCallback updateCourseList() {
|
||||
ActionCallback callback = new ActionCallback();
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
final List<CourseInfo> courses = EduStepicConnector.getCourses(null);
|
||||
final List<CourseInfo> cachedCourses = StudyProjectGenerator.getCoursesFromCache();
|
||||
StudyProjectGenerator.flushCache(courses);
|
||||
final List<Course> courses = EduStepicConnector.getCourses(null);
|
||||
StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis());
|
||||
|
||||
courses.removeAll(cachedCourses);
|
||||
if (!courses.isEmpty() && !cachedCourses.isEmpty()) {
|
||||
if (!courses.isEmpty()) {
|
||||
List<Course> updated = new ArrayList<>();
|
||||
for (Course course : courses) {
|
||||
if (course instanceof RemoteCourse && ((RemoteCourse)course).getUpdateDate().
|
||||
after(new Date(StudySettings.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, Course::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 +85,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;
|
||||
return timeToNextCheck <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ public class StepicUser {
|
||||
|
||||
@NotNull
|
||||
public String getAccessToken() {
|
||||
// for old project where authors were created with null tokens
|
||||
if (myAccessToken == null) {
|
||||
return "";
|
||||
}
|
||||
return myAccessToken;
|
||||
}
|
||||
|
||||
@@ -96,6 +100,10 @@ public class StepicUser {
|
||||
|
||||
@NotNull
|
||||
public String getRefreshToken() {
|
||||
// for old project where authors were created with null tokens
|
||||
if (myRefreshToken == null) {
|
||||
return "";
|
||||
}
|
||||
return myRefreshToken;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,25 +5,21 @@ 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;
|
||||
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.RemoteCourse;
|
||||
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;
|
||||
@@ -185,7 +171,7 @@ public class StepicWrappers {
|
||||
}
|
||||
|
||||
public static class CoursesContainer {
|
||||
public List<CourseInfo> courses;
|
||||
public List<RemoteCourse> courses;
|
||||
public Map meta;
|
||||
}
|
||||
|
||||
@@ -199,10 +185,10 @@ public class StepicWrappers {
|
||||
}
|
||||
|
||||
public static class CourseWrapper {
|
||||
CourseInfo course;
|
||||
RemoteCourse course;
|
||||
|
||||
public CourseWrapper(Course course) {
|
||||
this.course = new CourseInfo();
|
||||
this.course = new RemoteCourse();
|
||||
this.course.setName(course.getName());
|
||||
this.course.setDescription(course.getDescription());
|
||||
this.course.setAuthors(course.getAuthors());
|
||||
|
||||
+31
-32
@@ -29,7 +29,6 @@ import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.edu.learning.StudySettings;
|
||||
import com.jetbrains.edu.learning.StudyUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient;
|
||||
@@ -52,15 +51,16 @@ import java.util.List;
|
||||
* data: 7/31/14.
|
||||
*/
|
||||
public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
private List<CourseInfo> myAvailableCourses = new ArrayList<>();
|
||||
private List<Course> myAvailableCourses = new ArrayList<>();
|
||||
private JButton myBrowseButton;
|
||||
private ComboBox<CourseInfo> myCoursesComboBox;
|
||||
private ComboBox<Course> myCoursesComboBox;
|
||||
private JButton myRefreshButton;
|
||||
private JLabel myAuthorLabel;
|
||||
private JPanel myInfoPanel;
|
||||
private JTextPane myDescriptionPane;
|
||||
private JComponent myAnchor;
|
||||
private final StudyProjectGenerator myGenerator;
|
||||
private boolean isLocal = false;
|
||||
private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
|
||||
private static final String INVALID_COURSE = "Selected course is invalid";
|
||||
private FacetValidatorsManager myValidationManager;
|
||||
@@ -68,10 +68,10 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
private static final String LOGIN_TO_STEPIC_MESSAGE = "<html><u>Login to Stepik</u> to open the adaptive course </html>";
|
||||
private static final String LOGIN_TO_STEPIC = "Login to Stepik";
|
||||
|
||||
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
|
||||
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator, boolean isLocal) {
|
||||
super(new VerticalFlowLayout(true, true));
|
||||
myGenerator = generator;
|
||||
|
||||
this.isLocal = isLocal;
|
||||
layoutPanel();
|
||||
initListeners();
|
||||
}
|
||||
@@ -120,21 +120,21 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
|
||||
private void initCoursesCombobox() {
|
||||
myAvailableCourses =
|
||||
myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
|
||||
if (myAvailableCourses.contains(CourseInfo.INVALID_COURSE)) {
|
||||
myGenerator.getCoursesUnderProgress(!isLocal, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
|
||||
if (myAvailableCourses.contains(Course.INVALID_COURSE)) {
|
||||
setError(CONNECTION_ERROR);
|
||||
}
|
||||
else {
|
||||
addCoursesToCombobox(myAvailableCourses);
|
||||
final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
|
||||
final Course selectedCourse = StudyUtils.getFirst(myAvailableCourses);
|
||||
if (selectedCourse == null) return;
|
||||
setAuthors(selectedCourse);
|
||||
myDescriptionPane.setText(selectedCourse.getDescription());
|
||||
myDescriptionPane.setEditable(false);
|
||||
//setting the first course in list as selected
|
||||
myGenerator.setSelectedCourse(selectedCourse);
|
||||
if (myGenerator.getSelectedCourseInfo() != null) {
|
||||
myCoursesComboBox.setSelectedItem(myGenerator.getSelectedCourseInfo());
|
||||
if (myGenerator.getSelectedCourse() != null) {
|
||||
myCoursesComboBox.setSelectedItem(myGenerator.getSelectedCourse());
|
||||
}
|
||||
if (selectedCourse.isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
setError(LOGIN_TO_STEPIC_MESSAGE);
|
||||
@@ -173,7 +173,7 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
file -> {
|
||||
String fileName = file.getPath();
|
||||
int oldSize = myAvailableCourses.size();
|
||||
CourseInfo courseInfo = myGenerator.addLocalCourse(fileName);
|
||||
Course courseInfo = myGenerator.addLocalCourse(fileName);
|
||||
if (courseInfo != null) {
|
||||
if (oldSize != myAvailableCourses.size()) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
@@ -184,11 +184,11 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
else {
|
||||
setError(INVALID_COURSE);
|
||||
myCoursesComboBox.removeAllItems();
|
||||
myCoursesComboBox.addItem(CourseInfo.INVALID_COURSE);
|
||||
for (CourseInfo course : myAvailableCourses) {
|
||||
myCoursesComboBox.addItem(Course.INVALID_COURSE);
|
||||
for (Course course : myAvailableCourses) {
|
||||
myCoursesComboBox.addItem(course);
|
||||
}
|
||||
myCoursesComboBox.setSelectedItem(CourseInfo.INVALID_COURSE);
|
||||
myCoursesComboBox.setSelectedItem(Course.INVALID_COURSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -220,8 +220,8 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
isComboboxInitialized = true;
|
||||
initCoursesCombobox();
|
||||
}
|
||||
CourseInfo selectedCourse = (CourseInfo)myCoursesComboBox.getSelectedItem();
|
||||
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
|
||||
Course selectedCourse = (Course)myCoursesComboBox.getSelectedItem();
|
||||
if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) {
|
||||
setError(CONNECTION_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -255,9 +255,9 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
myAnchor = anchor;
|
||||
}
|
||||
|
||||
public void updateInfoPanel(@NotNull CourseInfo selectedCourseInfo) {
|
||||
setAuthors(selectedCourseInfo);
|
||||
myDescriptionPane.setText(selectedCourseInfo.getDescription());
|
||||
public void updateInfoPanel(@NotNull Course selectedCourse) {
|
||||
setAuthors(selectedCourse);
|
||||
myDescriptionPane.setText(selectedCourse.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,15 +268,15 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
private class RefreshActionListener implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final List<CourseInfo> courses =
|
||||
final List<Course> courses =
|
||||
myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
|
||||
if (!courses.contains(CourseInfo.INVALID_COURSE)) {
|
||||
if (!courses.contains(Course.INVALID_COURSE)) {
|
||||
refreshCoursesList(courses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
|
||||
private void refreshCoursesList(@NotNull final List<Course> courses) {
|
||||
if (courses.isEmpty()) {
|
||||
setError(CONNECTION_ERROR);
|
||||
return;
|
||||
@@ -284,17 +284,16 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
myCoursesComboBox.removeAllItems();
|
||||
|
||||
addCoursesToCombobox(courses);
|
||||
final CourseInfo selectedCourse = StudyUtils.getFirst(courses);
|
||||
final Course selectedCourse = StudyUtils.getFirst(courses);
|
||||
if (selectedCourse == null) return;
|
||||
myGenerator.setSelectedCourse(selectedCourse);
|
||||
|
||||
myGenerator.setCourses(courses);
|
||||
myAvailableCourses = courses;
|
||||
StudyProjectGenerator.flushCache(myAvailableCourses, false);
|
||||
}
|
||||
|
||||
private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
|
||||
for (CourseInfo courseInfo : courses) {
|
||||
private void addCoursesToCombobox(@NotNull List<Course> courses) {
|
||||
for (Course courseInfo : courses) {
|
||||
myCoursesComboBox.addItem(courseInfo);
|
||||
}
|
||||
}
|
||||
@@ -309,15 +308,15 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JComboBox cb = (JComboBox)e.getSource();
|
||||
CourseInfo selectedCourse = (CourseInfo)cb.getSelectedItem();
|
||||
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
|
||||
Course selectedCourse = (Course)cb.getSelectedItem();
|
||||
if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) {
|
||||
myAuthorLabel.setText("");
|
||||
myDescriptionPane.setText("");
|
||||
setError(INVALID_COURSE);
|
||||
return;
|
||||
}
|
||||
updateInfoPanel(selectedCourse);
|
||||
myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
|
||||
myCoursesComboBox.removeItem(Course.INVALID_COURSE);
|
||||
myGenerator.setSelectedCourse(selectedCourse);
|
||||
|
||||
setOK();
|
||||
@@ -329,12 +328,12 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
}
|
||||
}
|
||||
|
||||
private void setAuthors(CourseInfo selectedCourse) {
|
||||
private void setAuthors(Course selectedCourse) {
|
||||
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
|
||||
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
|
||||
}
|
||||
|
||||
public JComboBox<CourseInfo> getCoursesComboBox() {
|
||||
public JComboBox<Course> getCoursesComboBox() {
|
||||
return myCoursesComboBox;
|
||||
}
|
||||
|
||||
@@ -367,7 +366,7 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor {
|
||||
stepicUser.setPassword(myLoginPanel.getPassword());
|
||||
StudySettings.getInstance().setUser(stepicUser);
|
||||
myGenerator.setEnrolledCoursesIds(EduAdaptiveStepicConnector.getEnrolledCoursesIds(stepicUser));
|
||||
final List<CourseInfo> courses = myGenerator.getCourses(true);
|
||||
final List<Course> courses = myGenerator.getCourses(true);
|
||||
if (courses != null && myRefreshCourseList) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<component name="StudySettings">
|
||||
<StudyTaskManager>
|
||||
<option name="course">
|
||||
<Course>
|
||||
<option name="adaptive" value="false" />
|
||||
<option name="authors">
|
||||
<list>
|
||||
<StepicUser>
|
||||
<option name="accessToken" />
|
||||
<option name="email" value="" />
|
||||
<option name="firstName" value="ktisha" />
|
||||
<option name="id" value="-1" />
|
||||
<option name="lastName" value="" />
|
||||
<option name="refreshToken" />
|
||||
</StepicUser>
|
||||
</list>
|
||||
</option>
|
||||
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled31" />
|
||||
<option name="courseMode" value="Course Creator" />
|
||||
<option name="courseType" value="PyCharm" />
|
||||
<option name="description" value="test project" />
|
||||
<option name="id" value="0" />
|
||||
<option name="language" value="Python" />
|
||||
<option name="lessons">
|
||||
<list>
|
||||
<Lesson>
|
||||
<option name="id" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="name" value="lesson1" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskList">
|
||||
<list>
|
||||
<Task>
|
||||
<option name="activeSubtaskIndex" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="lastSubtaskIndex" value="0" />
|
||||
<option name="name" value="task1" />
|
||||
<option name="status" value="Unchecked" />
|
||||
<option name="stepId" value="0" />
|
||||
<option name="text" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskFiles">
|
||||
<map>
|
||||
<entry key="task.py">
|
||||
<value>
|
||||
<TaskFile>
|
||||
<option name="answerPlaceholders">
|
||||
<list>
|
||||
<AnswerPlaceholder>
|
||||
<option name="index" value="0" />
|
||||
<option name="initialState" />
|
||||
<option name="length" value="4" />
|
||||
<option name="offset" value="42" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="subtaskInfos">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<AnswerPlaceholderSubtaskInfo>
|
||||
<option name="answer" value="" />
|
||||
<option name="hasFrame" value="true" />
|
||||
<option name="hints">
|
||||
<list>
|
||||
<option value="hint" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="needInsertText" value="false" />
|
||||
<option name="placeholderText" value="type" />
|
||||
<option name="possibleAnswer" value="pass" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="status" value="Unchecked" />
|
||||
</AnswerPlaceholderSubtaskInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="useLength" value="false" />
|
||||
</AnswerPlaceholder>
|
||||
</list>
|
||||
</option>
|
||||
<option name="highlightErrors" value="true" />
|
||||
<option name="index" value="1" />
|
||||
<option name="trackChanges" value="true" />
|
||||
<option name="trackLengths" value="true" />
|
||||
<option name="userCreated" value="false" />
|
||||
<option name="name" value="task.py" />
|
||||
<option name="text" />
|
||||
</TaskFile>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</Task>
|
||||
</list>
|
||||
</option>
|
||||
</Lesson>
|
||||
</list>
|
||||
</option>
|
||||
<option name="name" value="test" />
|
||||
<option name="updateDate" />
|
||||
</Course>
|
||||
</option>
|
||||
<option name="enableTestingFromSamples" value="false" />
|
||||
<option name="toolWindowMode" value="TEXT" />
|
||||
<option name="turnEditingMode" value="false" />
|
||||
<option name="VERSION" value="4" />
|
||||
<option name="myUserTests">
|
||||
<map />
|
||||
</option>
|
||||
<option name="myInvisibleFiles">
|
||||
<list />
|
||||
</option>
|
||||
<option name="myShouldUseJavaFx" value="true" />
|
||||
</StudyTaskManager>
|
||||
</component>
|
||||
@@ -0,0 +1,117 @@
|
||||
<component name="StudySettings">
|
||||
<StudyTaskManager>
|
||||
<option name="course">
|
||||
<Course>
|
||||
<option name="adaptive" value="false" />
|
||||
<option name="authors">
|
||||
<list>
|
||||
<StepicUser>
|
||||
<option name="accessToken" />
|
||||
<option name="email" value="" />
|
||||
<option name="firstName" value="ktisha" />
|
||||
<option name="id" value="-1" />
|
||||
<option name="lastName" value="" />
|
||||
<option name="refreshToken" />
|
||||
</StepicUser>
|
||||
</list>
|
||||
</option>
|
||||
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled31" />
|
||||
<option name="courseMode" value="Course Creator" />
|
||||
<option name="courseType" value="PyCharm" />
|
||||
<option name="description" value="test project" />
|
||||
<option name="id" value="0" />
|
||||
<option name="language" value="Python" />
|
||||
<option name="lessons">
|
||||
<list>
|
||||
<Lesson>
|
||||
<option name="id" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="name" value="lesson1" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskList">
|
||||
<list>
|
||||
<Task>
|
||||
<option name="activeSubtaskIndex" value="0" />
|
||||
<option name="adaptiveTaskParameters" />
|
||||
<option name="index" value="1" />
|
||||
<option name="lastSubtaskIndex" value="0" />
|
||||
<option name="name" value="task1" />
|
||||
<option name="status" value="Unchecked" />
|
||||
<option name="stepId" value="0" />
|
||||
<option name="text" />
|
||||
<option name="theoryTask" value="false" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskFiles">
|
||||
<map>
|
||||
<entry key="task.py">
|
||||
<value>
|
||||
<TaskFile>
|
||||
<option name="answerPlaceholders">
|
||||
<list>
|
||||
<AnswerPlaceholder>
|
||||
<option name="index" value="0" />
|
||||
<option name="initialState" />
|
||||
<option name="length" value="4" />
|
||||
<option name="offset" value="42" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="subtaskInfos">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<AnswerPlaceholderSubtaskInfo>
|
||||
<option name="answer" value="" />
|
||||
<option name="hasFrame" value="true" />
|
||||
<option name="hints">
|
||||
<list>
|
||||
<option value="hint" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="needInsertText" value="false" />
|
||||
<option name="placeholderText" value="type" />
|
||||
<option name="possibleAnswer" value="pass" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="status" value="Unchecked" />
|
||||
</AnswerPlaceholderSubtaskInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="useLength" value="false" />
|
||||
</AnswerPlaceholder>
|
||||
</list>
|
||||
</option>
|
||||
<option name="highlightErrors" value="true" />
|
||||
<option name="index" value="1" />
|
||||
<option name="trackChanges" value="true" />
|
||||
<option name="trackLengths" value="true" />
|
||||
<option name="userCreated" value="false" />
|
||||
<option name="name" value="task.py" />
|
||||
<option name="text" />
|
||||
</TaskFile>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</Task>
|
||||
</list>
|
||||
</option>
|
||||
</Lesson>
|
||||
</list>
|
||||
</option>
|
||||
<option name="name" value="test" />
|
||||
<option name="updateDate" />
|
||||
</Course>
|
||||
</option>
|
||||
<option name="enableTestingFromSamples" value="false" />
|
||||
<option name="toolWindowMode" value="TEXT" />
|
||||
<option name="turnEditingMode" value="false" />
|
||||
<option name="VERSION" value="4" />
|
||||
<option name="myUserTests">
|
||||
<map />
|
||||
</option>
|
||||
<option name="myInvisibleFiles">
|
||||
<list />
|
||||
</option>
|
||||
<option name="myShouldUseJavaFx" value="true" />
|
||||
</StudyTaskManager>
|
||||
</component>
|
||||
@@ -0,0 +1,115 @@
|
||||
<component name="StudySettings">
|
||||
<StudyTaskManager>
|
||||
<option name="course">
|
||||
<Course>
|
||||
<option name="adaptive" value="false" />
|
||||
<option name="authors">
|
||||
<list>
|
||||
<StepicUser>
|
||||
<option name="accessToken" />
|
||||
<option name="email" value="" />
|
||||
<option name="firstName" value="ktisha" />
|
||||
<option name="id" value="-1" />
|
||||
<option name="lastName" value="" />
|
||||
<option name="refreshToken" />
|
||||
</StepicUser>
|
||||
</list>
|
||||
</option>
|
||||
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled30" />
|
||||
<option name="courseMode" value="Course Creator" />
|
||||
<option name="courseType" value="PyCharm" />
|
||||
<option name="description" value="test project" />
|
||||
<option name="id" value="0" />
|
||||
<option name="language" value="Python" />
|
||||
<option name="lessons">
|
||||
<list>
|
||||
<Lesson>
|
||||
<option name="id" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="name" value="lesson1" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskList">
|
||||
<list>
|
||||
<Task>
|
||||
<option name="activeSubtaskIndex" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="lastSubtaskIndex" value="0" />
|
||||
<option name="name" value="task1" />
|
||||
<option name="status" value="Unchecked" />
|
||||
<option name="stepId" value="0" />
|
||||
<option name="text" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskFiles">
|
||||
<map>
|
||||
<entry key="task.py">
|
||||
<value>
|
||||
<TaskFile>
|
||||
<option name="answerPlaceholders">
|
||||
<list>
|
||||
<AnswerPlaceholder>
|
||||
<option name="index" value="0" />
|
||||
<option name="initialState" />
|
||||
<option name="length" value="5" />
|
||||
<option name="offset" value="42" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="subtaskInfos">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<AnswerPlaceholderSubtaskInfo>
|
||||
<option name="answer" value="" />
|
||||
<option name="hasFrame" value="true" />
|
||||
<option name="hints">
|
||||
<list>
|
||||
<option value="hint1" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="needInsertText" value="false" />
|
||||
<option name="placeholderText" value="place" />
|
||||
<option name="possibleAnswer" value="pass" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="status" value="Unchecked" />
|
||||
</AnswerPlaceholderSubtaskInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="useLength" value="false" />
|
||||
</AnswerPlaceholder>
|
||||
</list>
|
||||
</option>
|
||||
<option name="highlightErrors" value="true" />
|
||||
<option name="index" value="1" />
|
||||
<option name="trackChanges" value="true" />
|
||||
<option name="trackLengths" value="true" />
|
||||
<option name="userCreated" value="false" />
|
||||
<option name="name" value="task.py" />
|
||||
<option name="text" />
|
||||
</TaskFile>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</Task>
|
||||
</list>
|
||||
</option>
|
||||
</Lesson>
|
||||
</list>
|
||||
</option>
|
||||
<option name="name" value="test" />
|
||||
<option name="updateDate" />
|
||||
</Course>
|
||||
</option>
|
||||
<option name="enableTestingFromSamples" value="false" />
|
||||
<option name="toolWindowMode" value="TEXT" />
|
||||
<option name="turnEditingMode" value="false" />
|
||||
<option name="VERSION" value="4" />
|
||||
<option name="myUserTests">
|
||||
<map />
|
||||
</option>
|
||||
<option name="myInvisibleFiles">
|
||||
<list />
|
||||
</option>
|
||||
<option name="myShouldUseJavaFx" value="true" />
|
||||
</StudyTaskManager>
|
||||
</component>
|
||||
@@ -0,0 +1,117 @@
|
||||
<component name="StudySettings">
|
||||
<StudyTaskManager>
|
||||
<option name="course">
|
||||
<Course>
|
||||
<option name="adaptive" value="false" />
|
||||
<option name="authors">
|
||||
<list>
|
||||
<StepicUser>
|
||||
<option name="accessToken" />
|
||||
<option name="email" value="" />
|
||||
<option name="firstName" value="ktisha" />
|
||||
<option name="id" value="-1" />
|
||||
<option name="lastName" value="" />
|
||||
<option name="refreshToken" />
|
||||
</StepicUser>
|
||||
</list>
|
||||
</option>
|
||||
<option name="courseDirectory" value="$APPLICATION_CONFIG_DIR$/courses/test-untitled30" />
|
||||
<option name="courseMode" value="Course Creator" />
|
||||
<option name="courseType" value="PyCharm" />
|
||||
<option name="description" value="test project" />
|
||||
<option name="id" value="0" />
|
||||
<option name="language" value="Python" />
|
||||
<option name="lessons">
|
||||
<list>
|
||||
<Lesson>
|
||||
<option name="id" value="0" />
|
||||
<option name="index" value="1" />
|
||||
<option name="name" value="lesson1" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskList">
|
||||
<list>
|
||||
<Task>
|
||||
<option name="activeSubtaskIndex" value="0" />
|
||||
<option name="adaptiveTaskParameters" />
|
||||
<option name="index" value="1" />
|
||||
<option name="lastSubtaskIndex" value="0" />
|
||||
<option name="name" value="task1" />
|
||||
<option name="status" value="Unchecked" />
|
||||
<option name="stepId" value="0" />
|
||||
<option name="text" />
|
||||
<option name="theoryTask" value="false" />
|
||||
<option name="updateDate" />
|
||||
<option name="taskFiles">
|
||||
<map>
|
||||
<entry key="task.py">
|
||||
<value>
|
||||
<TaskFile>
|
||||
<option name="answerPlaceholders">
|
||||
<list>
|
||||
<AnswerPlaceholder>
|
||||
<option name="index" value="0" />
|
||||
<option name="initialState" />
|
||||
<option name="length" value="5" />
|
||||
<option name="offset" value="42" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="subtaskInfos">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<AnswerPlaceholderSubtaskInfo>
|
||||
<option name="answer" value="" />
|
||||
<option name="hasFrame" value="true" />
|
||||
<option name="hints">
|
||||
<list>
|
||||
<option value="hint1" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="needInsertText" value="false" />
|
||||
<option name="placeholderText" value="place" />
|
||||
<option name="possibleAnswer" value="pass" />
|
||||
<option name="selected" value="false" />
|
||||
<option name="status" value="Unchecked" />
|
||||
</AnswerPlaceholderSubtaskInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="useLength" value="false" />
|
||||
</AnswerPlaceholder>
|
||||
</list>
|
||||
</option>
|
||||
<option name="highlightErrors" value="true" />
|
||||
<option name="index" value="1" />
|
||||
<option name="trackChanges" value="true" />
|
||||
<option name="trackLengths" value="true" />
|
||||
<option name="userCreated" value="false" />
|
||||
<option name="name" value="task.py" />
|
||||
<option name="text" />
|
||||
</TaskFile>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</Task>
|
||||
</list>
|
||||
</option>
|
||||
</Lesson>
|
||||
</list>
|
||||
</option>
|
||||
<option name="name" value="test" />
|
||||
<option name="updateDate" />
|
||||
</Course>
|
||||
</option>
|
||||
<option name="enableTestingFromSamples" value="false" />
|
||||
<option name="toolWindowMode" value="TEXT" />
|
||||
<option name="turnEditingMode" value="false" />
|
||||
<option name="VERSION" value="4" />
|
||||
<option name="myUserTests">
|
||||
<map />
|
||||
</option>
|
||||
<option name="myInvisibleFiles">
|
||||
<list />
|
||||
</option>
|
||||
<option name="myShouldUseJavaFx" value="true" />
|
||||
</StudyTaskManager>
|
||||
</component>
|
||||
@@ -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();
|
||||
@@ -91,7 +90,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
|
||||
task.setIndex(1);
|
||||
lesson.addTask(task);
|
||||
lesson.setIndex(1);
|
||||
course.getLessons().add(lesson);
|
||||
course.addLesson(lesson);
|
||||
course.setCourseMode(CCUtils.COURSE_MODE);
|
||||
course.initCourse(false);
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@@ -118,6 +117,8 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
|
||||
taskFile.setTask(task);
|
||||
task.getTaskFiles().put(name, taskFile);
|
||||
VirtualFile file = copyFileToTask(name);
|
||||
|
||||
taskFile.name = name;
|
||||
myFixture.configureFromExistingVirtualFile(file);
|
||||
Document document = FileDocumentManager.getInstance().getDocument(file);
|
||||
for (AnswerPlaceholder placeholder : getPlaceholders(document, false)) {
|
||||
@@ -125,7 +126,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;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase {
|
||||
}
|
||||
document.deleteString(closingMatcher.start(), closingMatcher.end());
|
||||
document.deleteString(openingMatcher.start(), openingMatcher.end());
|
||||
FileDocumentManager.getInstance().saveDocument(document);
|
||||
placeholders.add(answerPlaceholder);
|
||||
pos = answerPlaceholder.getOffset() + answerPlaceholder.getRealLength();
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class CCShowPreviewTest extends CCTestCase {
|
||||
action.actionPerformed(e);
|
||||
Editor editor = EditorFactory.getInstance().getAllEditors()[1];
|
||||
Pair<Document, List<AnswerPlaceholder>> pair = getPlaceholders(name + CCTestsUtil.AFTER_POSTFIX);
|
||||
assertEquals("Files don't match", editor.getDocument().getText(), pair.getFirst().getText());
|
||||
assertEquals("Files don't match", pair.getFirst().getText(), editor.getDocument().getText());
|
||||
for (AnswerPlaceholder placeholder : pair.getSecond()) {
|
||||
assertNotNull("No highlighter for placeholder:" + CCTestsUtil.getPlaceholderPresentation(placeholder), getHighlighter(editor.getMarkupModel(), placeholder));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.intellij.util.JdomKt;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.JDOMException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -14,22 +16,51 @@ import java.nio.file.Paths;
|
||||
import static com.intellij.testFramework.assertions.Assertions.assertThat;
|
||||
|
||||
public class StudyMigrationTest {
|
||||
@Rule public TestName name = new TestName();
|
||||
|
||||
@Test
|
||||
public void testFromThirdToForth() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
Element element = JdomKt.loadElement(getTestDataPath().resolve("3.xml"));
|
||||
assertThat(StudySerializationUtils.Xml.convertToForthVersion(element)).isEqualTo(getTestDataPath().resolve("4.xml"));
|
||||
doTest(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdaptive45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
Element element = JdomKt.loadElement(getTestDataPath().resolve("adaptive4.xml"));
|
||||
assertThat(StudySerializationUtils.Xml.convertToFifthVersion(element)).isEqualTo(getTestDataPath().resolve("adaptive5.xml"));
|
||||
doTest(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubtasks45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
Element element = JdomKt.loadElement(getTestDataPath().resolve("subtasks4.xml"));
|
||||
assertThat(StudySerializationUtils.Xml.convertToFifthVersion(element)).isEqualTo(getTestDataPath().resolve("subtasks5.xml"));
|
||||
doTest(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTheory35To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
doTest(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTheory351To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
doTest(4);
|
||||
}
|
||||
|
||||
private void doTest(int version) throws IOException, JDOMException, StudySerializationUtils.StudyUnrecognizedFormatException {
|
||||
final String name = PlatformTestUtil.getTestName(this.name.getMethodName(), true);
|
||||
final Path before = getTestDataPath().resolve(name + ".xml");
|
||||
final Path after = getTestDataPath().resolve(name + ".after.xml");
|
||||
Element element = JdomKt.loadElement(before);
|
||||
Element converted = element;
|
||||
switch (version) {
|
||||
case 1:
|
||||
converted = StudySerializationUtils.Xml.convertToSecondVersion(element);
|
||||
break;
|
||||
case 3:
|
||||
converted = StudySerializationUtils.Xml.convertToForthVersion(element);
|
||||
break;
|
||||
case 4:
|
||||
converted = StudySerializationUtils.Xml.convertToFifthVersion(element);
|
||||
break;
|
||||
}
|
||||
assertThat(converted).isEqualTo(after);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.edu.learning.StudySerializationUtils;
|
||||
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
|
||||
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
|
||||
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
|
||||
import com.jetbrains.edu.learning.courseFormat.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.courseFormat.TaskFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
@@ -65,9 +65,9 @@ public class StudyStepicFormatTest {
|
||||
EduStepicClient.deserializeStepicResponse(StepicWrappers.CoursesContainer.class, responseString);
|
||||
assertNotNull(container.courses);
|
||||
assertTrue("Incorrect number of courses", container.courses.size() == 4);
|
||||
List<CourseInfo> filtered = ContainerUtil.filter(container.courses, info -> EduStepicConnector.canBeOpened(info));
|
||||
List<RemoteCourse> filtered = ContainerUtil.filter(container.courses, info -> EduStepicConnector.canBeOpened(info));
|
||||
assertEquals(ContainerUtil.newArrayList("Adaptive Python", "Introduction to Python", "format2"),
|
||||
ContainerUtil.map(filtered, CourseInfo::getName));
|
||||
ContainerUtil.map(filtered, RemoteCourse::getName));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Binary file not shown.
+7
-8
@@ -11,7 +11,6 @@ import com.jetbrains.edu.learning.StudyTaskManager;
|
||||
import com.jetbrains.edu.learning.core.EduNames;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.courseFormat.CourseInfo;
|
||||
import com.jetbrains.python.newProject.steps.PyCharmNewProjectStep;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -25,7 +24,7 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
|
||||
|
||||
private final Project myProject;
|
||||
private final Module myModule;
|
||||
private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator();
|
||||
private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator(true);
|
||||
|
||||
public MyCustomization(Project project,
|
||||
Module module) {
|
||||
@@ -44,10 +43,10 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
|
||||
@NotNull
|
||||
@Override
|
||||
protected DirectoryProjectGenerator createEmptyProjectGenerator() {
|
||||
Course course = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
if (course != null) {
|
||||
Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
|
||||
if (currentCourse != null) {
|
||||
VirtualFile folder = CCUtils.getGeneratedFilesFolder(myProject, myModule);
|
||||
String zipName = FileUtil.sanitizeFileName(course.getName());
|
||||
String zipName = FileUtil.sanitizeFileName(currentCourse.getName());
|
||||
if (zipName.isEmpty()) {
|
||||
zipName = EduNames.COURSE;
|
||||
}
|
||||
@@ -55,9 +54,9 @@ class CreateFromArchiveProjectStep extends PyCharmNewProjectStep {
|
||||
CCCreateCourseArchive.createCourseArchive(myProject, myModule, zipName, locationDir, false);
|
||||
String path = FileUtil.join(FileUtil.toSystemDependentName(folder.getPath()), zipName + ".zip");
|
||||
StudyProjectGenerator generator = myGenerator.getGenerator();
|
||||
CourseInfo info = generator.addLocalCourse(path);
|
||||
assert info != null;
|
||||
generator.setSelectedCourse(info);
|
||||
Course course = generator.addLocalCourse(path);
|
||||
assert course != null;
|
||||
generator.setSelectedCourse(course);
|
||||
}
|
||||
return myGenerator;
|
||||
}
|
||||
|
||||
+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);
|
||||
|
||||
+21
-3
@@ -14,22 +14,27 @@ 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PyEduPluginConfigurator implements EduPluginConfigurator {
|
||||
public static final String PYTHON_3 = "3.x";
|
||||
public static final String PYTHON_2 = "2.x";
|
||||
private static final String TESTS_PY = "tests.py";
|
||||
private static final Logger LOG = Logger.getInstance(PyEduPluginConfigurator.class);
|
||||
private static final String COURSE_NAME = "Introduction to Python.zip";
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -65,9 +70,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 path) {
|
||||
return path.contains("__pycache__") || path.endsWith(".pyc");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,4 +130,18 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator {
|
||||
public StudyCheckAction getCheckAction() {
|
||||
return new PyStudyCheckAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getBundledCoursePaths() {
|
||||
@NonNls String jarPath = PathUtil.getJarPathForClass(PyEduPluginConfigurator.class);
|
||||
|
||||
if (jarPath.endsWith(".jar")) {
|
||||
final File jarFile = new File(jarPath);
|
||||
|
||||
File pluginBaseDir = jarFile.getParentFile();
|
||||
return Collections.singletonList(new File(new File(pluginBaseDir, "courses"), COURSE_NAME).getPath());
|
||||
}
|
||||
|
||||
return Collections.singletonList(new File(new File(jarPath, "courses"), COURSE_NAME).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);
|
||||
|
||||
+17
-16
@@ -23,8 +23,9 @@ 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.courseFormat.RemoteCourse;
|
||||
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
|
||||
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
|
||||
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
|
||||
@@ -58,7 +59,12 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
public ValidationResult myValidationResult = new ValidationResult("selected course is not valid");
|
||||
private StudyNewProjectPanel mySettingsPanel;
|
||||
|
||||
@SuppressWarnings("unused") // used on startup
|
||||
public PyStudyDirectoryProjectGenerator() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public PyStudyDirectoryProjectGenerator(boolean isLocal) {
|
||||
myGenerator = new StudyProjectGenerator();
|
||||
myGenerator.addSettingsStateListener(new StudyProjectGenerator.SettingsListener() {
|
||||
@Override
|
||||
@@ -67,7 +73,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
}
|
||||
});
|
||||
|
||||
mySettingsPanel = new StudyNewProjectPanel(myGenerator);
|
||||
mySettingsPanel = new StudyNewProjectPanel(myGenerator, isLocal);
|
||||
mySettingsPanel.registerValidators(new FacetValidatorsManager() {
|
||||
public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -82,7 +88,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
if (selectedItem != null && ((CourseInfo)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
mySettingsPanel.showLoginDialog(false, "Signing In");
|
||||
}
|
||||
}
|
||||
@@ -90,14 +96,14 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
if (selectedItem != null && ((CourseInfo)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
final CourseInfo selectedItem = (CourseInfo)mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
final Course selectedItem = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
if (selectedItem != null && selectedItem.isAdaptive() && !myGenerator.isLoggedIn()) {
|
||||
e.getComponent().setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
@@ -125,7 +131,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,12 +166,7 @@ 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) {
|
||||
public void setSelectedCourse(Course course) {
|
||||
myGenerator.setSelectedCourse(course);
|
||||
}
|
||||
|
||||
@@ -183,12 +184,12 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator<PyN
|
||||
public BooleanFunction<PythonProjectGenerator> beforeProjectGenerated(@Nullable Sdk sdk) {
|
||||
return generator -> {
|
||||
final List<Integer> enrolledCoursesIds = myGenerator.getEnrolledCoursesIds();
|
||||
final CourseInfo course = (CourseInfo)mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
if (course == null) return true;
|
||||
if (course.getId() > 0 && !enrolledCoursesIds.contains(course.getId())) {
|
||||
final Course course = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem();
|
||||
if (course == null || !(course instanceof RemoteCourse)) return true;
|
||||
if (((RemoteCourse)course).getId() > 0 && !enrolledCoursesIds.contains(((RemoteCourse)course).getId())) {
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
|
||||
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(course.getId(),
|
||||
return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(((RemoteCourse)course).getId(),
|
||||
StudySettings.getInstance().getUser()));
|
||||
}, "Creating Course", true, ProjectManager.getInstance().getDefaultProject());
|
||||
|
||||
|
||||
+6
-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,10 @@ 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);
|
||||
propertiesComponent.setValue(CONFIGURED_V40, "true");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
+12
-4
@@ -19,15 +19,19 @@ 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.PyEduPluginConfigurator;
|
||||
import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator;
|
||||
import com.jetbrains.edu.learning.courseFormat.Course;
|
||||
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;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class PyStudyIntroductionCourseAction extends AnAction {
|
||||
|
||||
@@ -44,7 +48,9 @@ public class PyStudyIntroductionCourseAction extends AnAction {
|
||||
if (projectDir.exists()) {
|
||||
return;
|
||||
}
|
||||
if (StudyProjectGenerator.getBundledIntro() != null) {
|
||||
final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(PythonLanguage.getInstance());
|
||||
final List<String> paths = configurator.getBundledCoursePaths();
|
||||
if (paths.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Presentation presentation = e.getPresentation();
|
||||
@@ -59,8 +65,10 @@ public class PyStudyIntroductionCourseAction extends AnAction {
|
||||
ProjectUtil.openProject(projectDir.getPath(), null, false);
|
||||
}
|
||||
else {
|
||||
final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator();
|
||||
CourseInfo introCourse = StudyProjectGenerator.getBundledIntro();
|
||||
final PyStudyDirectoryProjectGenerator generator = new PyStudyDirectoryProjectGenerator(true);
|
||||
final EduPluginConfigurator configurator = new PyEduPluginConfigurator();
|
||||
final String bundledCoursePath = configurator.getBundledCoursePaths().get(0);
|
||||
Course introCourse = StudyProjectGenerator.getCourse(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")) {
|
||||
|
||||
@@ -332,18 +332,15 @@ def _is_django_exception_break_context(frame):
|
||||
#=======================================================================================================================
|
||||
|
||||
def can_not_skip(plugin, main_debugger, pydb_frame, frame):
|
||||
if main_debugger.django_breakpoints and _is_django_render_call(frame):
|
||||
filename = _get_template_file_name(frame)
|
||||
django_breakpoints_for_file = main_debugger.django_breakpoints.get(filename)
|
||||
if django_breakpoints_for_file:
|
||||
return True
|
||||
return False
|
||||
return main_debugger.django_breakpoints and _is_django_render_call(frame)
|
||||
|
||||
|
||||
def has_exception_breaks(plugin):
|
||||
if len(plugin.main_debugger.django_exception_break) > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def has_line_breaks(plugin):
|
||||
for file, breakpoints in dict_iter_items(plugin.main_debugger.django_breakpoints):
|
||||
if len(breakpoints) > 0:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<!-- Components and extensions declared in this file work ONLY in the Python plugin for minor IDEs -->
|
||||
|
||||
<xi:include href="/META-INF/python-community-configure-common.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
<xi:include href="/META-INF/python-community-plugin-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<projectConfigurable groupId="build" groupWeight="118"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,7 +75,7 @@ public class BuildoutFacet extends LibraryContributingFacet<BuildoutFacetConfigu
|
||||
@NotNull final BuildoutFacetConfiguration configuration, Facet underlyingFacet) {
|
||||
super(facetType, module, name, configuration, underlyingFacet);
|
||||
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
|
||||
@Override
|
||||
public void contentsChanged(@NotNull VirtualFileEvent event) {
|
||||
if (Comparing.equal(event.getFile(), getScript())) {
|
||||
@@ -99,7 +99,7 @@ public class BuildoutFacet extends LibraryContributingFacet<BuildoutFacetConfigu
|
||||
bin.refresh(false, false);
|
||||
}
|
||||
final String exe;
|
||||
if (SystemInfo.isWindows || SystemInfo.isOS2) {
|
||||
if (SystemInfo.isWindows) {
|
||||
exe = "buildout.exe";
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,7 +51,7 @@ public class PythonModulePathCache extends PythonPathCache implements Disposable
|
||||
clearCache();
|
||||
}
|
||||
});
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileAdapter(), this);
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileListener(), this);
|
||||
updateCacheForSdk(module);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.vfs.*;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
@@ -27,7 +26,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -76,7 +74,7 @@ public abstract class PythonPathCache {
|
||||
myQNameCache.put(vFile.getUrl(), new ArrayList<>(qNames));
|
||||
}
|
||||
|
||||
protected class MyVirtualFileAdapter extends VirtualFileAdapter {
|
||||
protected class MyVirtualFileListener implements VirtualFileListener {
|
||||
@Override
|
||||
public void fileCreated(@NotNull VirtualFileEvent event) {
|
||||
clearCache();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -94,7 +94,7 @@ public class PythonSdkPathCache extends PythonPathCache implements Disposable {
|
||||
myBuiltins.set(null);
|
||||
}
|
||||
}, this);
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileAdapter(), this);
|
||||
VirtualFileManager.getInstance().addVirtualFileListener(new MyVirtualFileListener(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -375,7 +375,7 @@ public final class PythonSdkType extends SdkType {
|
||||
*/
|
||||
@Nullable
|
||||
public static File findExecutableFile(File parent, String name) {
|
||||
if (SystemInfo.isWindows || SystemInfo.isOS2) {
|
||||
if (SystemInfo.isWindows) {
|
||||
for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) {
|
||||
File file = new File(parent, name + "." + suffix);
|
||||
if (file.exists()) return file;
|
||||
|
||||
Reference in New Issue
Block a user