Resolve merge conflicts

This commit is contained in:
Valentina Kiryushkina
2016-06-15 16:04:25 +03:00
43 changed files with 2210 additions and 812 deletions
@@ -17,6 +17,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;
@@ -68,7 +69,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
private static void tryToRenameTaskFile(PsiFile file, String oldName) {
final PsiDirectory taskDir = file.getContainingDirectory();
Course course = StudyTaskManager.getInstance(file.getProject()).getCourse();
final Project project = file.getProject();
Course course = StudyTaskManager.getInstance(project).getCourse();
if (course == null) {
return;
}
@@ -221,7 +221,7 @@ public class CCUtils {
if (child == null) {
continue;
}
Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
Document patternDocument = StudyUtils.getPatternDocument(project, entry.getValue(), name);
Document document = FileDocumentManager.getInstance().getDocument(child);
if (document == null || patternDocument == null) {
return;
@@ -85,7 +85,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
}
private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, file.getName());
if (patternDocument == null) {
return;
}
@@ -24,7 +24,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
return;
}
String name = file.getName();
VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, name);
VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, name);
ApplicationManager.getApplication().runWriteAction(() -> {
if (patternFile != null) {
try {
@@ -87,11 +87,10 @@
<applicationConfigurable groupId="tools" instance="com.jetbrains.edu.learning.settings.StudyConfigurable"
id="com.jetbrains.edu.learning.settings.StudyConfigurable"
displayName="Education"/>
<applicationService serviceInterface="com.jetbrains.edu.learning.stepic.StudySettings"
serviceImplementation="com.jetbrains.edu.learning.stepic.StudySettings"/>
<toolWindow id="Task Description" anchor="right" factoryClass="com.jetbrains.edu.learning.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
<toolWindow id="Course Progress" anchor="left" factoryClass="com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
<toolWindow id="Test Results" anchor="bottom" factoryClass="com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
<fileEditorProvider implementation="com.jetbrains.edu.learning.editor.StudyFileEditorProvider"/>
<treeStructureProvider implementation="com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider"/>
<highlightErrorFilter implementation="com.jetbrains.edu.learning.editor.StudyHighlightErrorFilter"/>
@@ -66,6 +66,7 @@ public abstract class StudyBasePluginConfigurator implements StudyPluginConfigur
Task task = getTask(file);
setTaskText(task, file.getParent());
}
toolWindow.setBottomComponent(null);
}
@Nullable
@@ -14,6 +14,7 @@ import com.intellij.util.xmlb.XmlSerializer;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.*;
import com.jetbrains.edu.learning.oldCourseFormat.OldCourse;
import com.jetbrains.edu.learning.stepic.StepicUser;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -34,6 +35,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
private Course myCourse;
private OldCourse myOldCourse;
public int VERSION = 2;
public StepicUser myUser;
public Map<AnswerPlaceholder, StudyStatus> myStudyStatusMap = new HashMap<>();
public Map<TaskFile, StudyStatus> myTaskStatusMap = new HashMap<>();
public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
@@ -74,7 +76,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
@NotNull
public List<UserTest> getUserTests(@NotNull final Task task) {
final List<UserTest> userTests = myUserTests.get(task);
return userTests != null ? userTests : Collections.<UserTest>emptyList();
return userTests != null ? userTests : Collections.emptyList();
}
public void removeUserTest(@NotNull final Task task, @NotNull final UserTest userTest) {
@@ -212,6 +214,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
myTaskStatusMap = taskManager.myTaskStatusMap;
myStudyStatusMap = taskManager.myStudyStatusMap;
myShouldUseJavaFx = taskManager.myShouldUseJavaFx;
myUser = taskManager.getUser();
}
}
final Element oldCourseElement = state.getChild(COURSE_ELEMENT);
@@ -276,4 +279,38 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
public void setTurnEditingMode(boolean turnEditingMode) {
myTurnEditingMode = turnEditingMode;
}
public String getLogin() {
if (myUser != null) {
return myUser.getEmail();
}
return "";
}
public String getPassword() {
if (myUser != null) {
return myUser.getPassword();
}
return "";
}
public void setLogin(String login) {
if (myUser != null) {
myUser.setEmail(login);
}
}
public void setPassword(String password) {
if (myUser != null) {
myUser.setPassword(password);
}
}
public StepicUser getUser() {
return myUser;
}
public void setUser(StepicUser user) {
myUser = user;
}
}
@@ -20,7 +20,9 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Disposer;
@@ -36,6 +38,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.content.Content;
import com.intellij.util.TimeoutUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.MarkdownUtil;
@@ -47,6 +50,7 @@ import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
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.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
@@ -61,6 +65,9 @@ import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class StudyUtils {
private StudyUtils() {
@@ -358,12 +365,12 @@ public class StudyUtils {
@Nullable
public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
public static VirtualFile getPatternFile(@NotNull Project project, @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());
File resourceFile = getCourseDirectory(project, course);
if (!resourceFile.exists()) {
return null;
}
@@ -376,8 +383,8 @@ public class StudyUtils {
}
@Nullable
public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
VirtualFile patternFile = getPatternFile(taskFile, name);
public static Document getPatternDocument(@NotNull Project project, @NotNull final TaskFile taskFile, String name) {
VirtualFile patternFile = getPatternFile(project, taskFile, name);
if (patternFile == null) {
return null;
}
@@ -495,7 +502,7 @@ public class StudyUtils {
return null;
}
@Nullable
public static String getTaskText(@NotNull final Project project) {
TaskFile taskFile = getSelectedTaskFile(project);
if (taskFile == null) {
@@ -519,12 +526,33 @@ public class StudyUtils {
}
return taskFile;
}
@Nullable
public static Task getCurrentTask(@NotNull final Project project) {
VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
TaskFile taskFile = null;
for (VirtualFile file : files) {
taskFile = getTaskFile(project, file);
if (taskFile != null) {
break;
}
}
if (taskFile != null) {
return taskFile.getTask();
}
return null;
}
public static void updateStudyToolWindow(Project project) {
final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
if (studyToolWindow != null) {
String taskText = getTaskText(project);
studyToolWindow.setTaskText(taskText, null, project);
if (taskText != null) {
studyToolWindow.setTaskText(taskText, null, project);
}
else {
LOG.warn("Task text is null");
}
}
}
@@ -532,6 +560,32 @@ public class StudyUtils {
return StudyTaskManager.getInstance(project).getCourse() != null;
}
@Nullable
public static Project getStudyProject() {
Project studyProject = null;
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project project : openProjects) {
if (StudyTaskManager.getInstance(project).getCourse() != null) {
studyProject = project;
}
}
return studyProject;
}
@NotNull
public static File getCourseDirectory(@NotNull Project project, Course course) {
final File courseDirectory;
if (course.isAdaptive()) {
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
+ "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
}
else {
courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
}
return courseDirectory;
}
public static boolean hasJavaFx() {
try {
Class.forName("javafx.application.Platform");
@@ -575,6 +629,36 @@ public class StudyUtils {
return null;
}
// supposd to be called under progress
@Nullable
public static <T> T execCancelable(@NotNull final Callable<T> callable) {
final Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(callable);
while (!future.isCancelled() && !future.isDone()) {
ProgressManager.checkCanceled();
TimeoutUtil.sleep(500);
}
T result = null;
try {
result = future.get();
}
catch (InterruptedException | ExecutionException e) {
LOG.warn(e.getMessage());
}
return result;
}
@Nullable
public static Task getTaskFromSelectedEditor(Project project) {
final StudyEditor editor = getSelectedStudyEditor(project);
Task task = null;
if (editor != null) {
final TaskFile file = editor.getTaskFile();
task = file.getTask();
}
return task;
}
private static String convertToHtml(@NotNull final String content) {
ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
MarkdownUtil.replaceHeaders(lines);
@@ -45,7 +45,7 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
}
StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project);
final StudyState studyState = new StudyState(studyEditor);
Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
if (patternDocument == null) {
return;
}
@@ -81,7 +81,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
@NotNull final Project project,
TaskFile taskFile,
String name) {
if (!resetDocument(document, taskFile, name)) {
if (!resetDocument(project, document, taskFile, name)) {
return false;
}
taskFile.getTask().setStatus(StudyStatus.Unchecked);
@@ -110,10 +110,11 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
}
private static boolean resetDocument(@NotNull final Document document,
private static boolean resetDocument(@NotNull final Project project,
@NotNull final Document document,
@NotNull final TaskFile taskFile,
String fileName) {
final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName);
final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName);
if (patternDocument == null) {
return false;
}
@@ -8,6 +8,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -17,11 +18,13 @@ import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StudySettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
@@ -70,57 +73,132 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
@Override
public void run(@NotNull ProgressIndicator indicator) {
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null && course.isAdaptive()) {
checkForAdaptiveCourse(indicator);
}
else {
checkForEduCourse(indicator);
}
}
private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
if (testsOutput != null) {
if (testsOutput.isSuccess()) {
onTaskSolved(testsOutput.getMessage());
}
else {
onTaskFailed(testsOutput.getMessage());
}
runAfterTaskCheckedActions();
postAttemptToStepic(testsOutput);
}
}
@Nullable
private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
if (indicator.isCanceled()) {
ApplicationManager.getApplication().invokeLater(
() -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
return;
}
final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output);
String stderr = output.getStderr();
if (!stderr.isEmpty()) {
ApplicationManager.getApplication().invokeLater(() ->
StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
MessageType.WARNING.getPopupBackground(),
myProject));
//log error output of tests
LOG.info("#educational " + stderr);
return;
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null) {
final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
String stderr = output.getStderr();
if (!stderr.isEmpty()) {
ApplicationManager.getApplication().invokeLater(() ->
StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
MessageType.WARNING.getPopupBackground(),
myProject));
//log error output of tests
LOG.info("#educational " + stderr);
return null;
}
return testsOutput;
}
postAttemptToStepic(testsOutput);
if (testsOutput.isSuccess()) {
onTaskSolved(testsOutput);
}
else {
onTaskFailed(testsOutput);
}
runAfterTaskSolvedActions();
return null;
}
protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
private void checkForAdaptiveCourse(ProgressIndicator indicator) {
final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator);
if (testOutput != null) {
if (testOutput.isSuccess()) {
final Pair<Boolean, String> pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask);
if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) {
if (pair.getFirst()) {
onTaskSolved("Congratulations! Remote tests passed.");
if (myStatusBeforeCheck != StudyStatus.Solved) {
EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2);
}
}
else {
final String checkMessage = pair.getSecond();
onTaskFailed(checkMessage);
}
runAfterTaskCheckedActions();
}
else {
ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
MessageType.WARNING
.getPopupBackground(),
myProject));
}
}
else {
onTaskFailed(testOutput.getMessage());
}
}
}
protected void onTaskFailed(String message) {
myTaskManger.setStatus(myTask, StudyStatus.Failed);
ApplicationManager.getApplication().invokeLater(
() -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), myProject));
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null) {
if (course.isAdaptive()) {
ApplicationManager.getApplication().invokeLater(
() -> {
StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
});
}
else {
ApplicationManager.getApplication()
.invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
}
}
}
protected void onTaskSolved(StudyTestsOutputParser.TestsOutput testsOutput) {
protected void onTaskSolved(String message) {
myTaskManger.setStatus(myTask, StudyStatus.Solved);
ApplicationManager.getApplication().invokeLater(
() -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), myProject));
final Course course = StudyTaskManager.getInstance(myProject).getCourse();
if (course != null) {
if (course.isAdaptive()) {
ApplicationManager.getApplication().invokeLater(
() -> {
StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
});
}
else {
ApplicationManager.getApplication()
.invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject));
}
}
}
private void runAfterTaskSolvedActions() {
private void runAfterTaskCheckedActions() {
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
if (configurator != null) {
StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
if (checkActions != null) {
for (StudyAfterCheckAction action: checkActions) {
for (StudyAfterCheckAction action : checkActions) {
action.run(myProject, myTask, myStatusBeforeCheck);
}
}
@@ -130,8 +208,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
}
}
protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) {
final StudySettings studySettings = StudySettings.getInstance();
protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) {
final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject);
final String login = studySettings.getLogin();
final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword();
EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password);
@@ -1,5 +1,6 @@
package com.jetbrains.edu.learning.checker;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@@ -15,24 +16,26 @@ import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.*;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.ui.content.Content;
import com.jetbrains.edu.learning.StudyState;
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.core.EduUtils;
import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyState;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.navigation.StudyNavigator;
import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory;
import com.jetbrains.python.console.PythonConsoleView;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.List;
@@ -61,9 +64,9 @@ public class StudyCheckUtils {
}
public static void navigateToFailedPlaceholder(@NotNull final StudyState studyState,
@NotNull final Task task,
@NotNull final VirtualFile taskDir,
@NotNull final Project project) {
@NotNull final Task task,
@NotNull final VirtualFile taskDir,
@NotNull final Project project) {
TaskFile selectedTaskFile = studyState.getTaskFile();
Editor editor = studyState.getEditor();
TaskFile taskFileToNavigate = selectedTaskFile;
@@ -109,10 +112,10 @@ public class StudyCheckUtils {
public static void runSmartTestProcess(@NotNull final VirtualFile taskDir,
@NotNull final StudyTestRunner testRunner,
final String taskFileName,
@NotNull final TaskFile taskFile,
@NotNull final Project project) {
@NotNull final StudyTestRunner testRunner,
final String taskFileName,
@NotNull final TaskFile taskFile,
@NotNull final Project project) {
final TaskFile answerTaskFile = new TaskFile();
answerTaskFile.name = taskFileName;
final VirtualFile virtualFile = taskDir.findChild(taskFileName);
@@ -135,11 +138,10 @@ public class StudyCheckUtils {
}
private static VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir,
@NotNull final VirtualFile file,
@NotNull final TaskFile source,
@NotNull final TaskFile target) {
@NotNull final VirtualFile file,
@NotNull final TaskFile source,
@NotNull final TaskFile target) {
VirtualFile copy = null;
try {
@@ -191,4 +193,31 @@ public class StudyCheckUtils {
EduUtils.flushWindows(taskFile, virtualFile, true);
}
}
public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message, boolean solved) {
final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
ToolWindow window = toolWindowManager.getToolWindow("Test Results");
if (window == null) {
toolWindowManager.registerToolWindow("Test Results", true, ToolWindowAnchor.BOTTOM);
window = toolWindowManager.getToolWindow("Test Results");
new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window);
}
final Content[] contents = window.getContentManager().getContents();
for (Content content : contents) {
final JComponent component = content.getComponent();
if (component instanceof PythonConsoleView) {
((PythonConsoleView)component).clear();
if (!solved) {
((PythonConsoleView)component).print(message, ConsoleViewContentType.ERROR_OUTPUT);
}
else {
((PythonConsoleView)component).print(message, ConsoleViewContentType.NORMAL_OUTPUT);
}
window.setAvailable(true, () -> {});
window.show(() -> {});
return;
}
}
}
}
@@ -14,6 +14,7 @@ import com.jetbrains.edu.learning.core.EduDocumentListener;
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.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
@@ -65,14 +66,17 @@ public class StudySmartChecker {
Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath());
final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath());
final ProcessOutput output = handler.runProcess();
boolean res = StudyTestsOutputParser.getTestsOutput(output).isSuccess();
StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
StudyUtils.deleteFile(windowCopy);
if (fileWindows != null) {
StudyUtils.deleteFile(fileWindows);
}
if (!resourceFile.delete()) {
LOG.error("failed to delete", resourceFile.getPath());
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null) {
boolean res = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()).isSuccess();
StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
StudyUtils.deleteFile(windowCopy);
if (fileWindows != null) {
StudyUtils.deleteFile(fileWindows);
}
if (!resourceFile.delete()) {
LOG.error("failed to delete", resourceFile.getPath());
}
}
}
}
@@ -3,8 +3,10 @@ package com.jetbrains.edu.learning.checker;
import com.intellij.execution.process.ProcessOutput;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class StudyTestsOutputParser {
private static final String ourStudyPrefix = "#educational_plugin";
private static final String STUDY_PREFIX = "#educational_plugin";
public static final String TEST_OK = "test OK";
private static final String TEST_FAILED = "FAILED + ";
private static final String CONGRATS_MESSAGE = "CONGRATS_MESSAGE ";
@@ -29,10 +31,12 @@ public class StudyTestsOutputParser {
}
@NotNull
public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) {
public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) {
String congratulations = CONGRATULATIONS;
for (String line : processOutput.getStdoutLines()) {
if (line.startsWith(ourStudyPrefix)) {
final List<String> lines = processOutput.getStdoutLines();
for (int i = 0; i < lines.size(); i++) {
final String line = lines.get(i);
if (line.startsWith(STUDY_PREFIX)) {
if (line.contains(TEST_OK)) {
continue;
}
@@ -42,7 +46,23 @@ public class StudyTestsOutputParser {
}
if (line.contains(TEST_FAILED)) {
return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
if (!isAdaptive) {
return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
}
else {
final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n");
for (int j = i + 1; j < lines.size(); j++) {
final String failedTextLine = lines.get(j);
if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) {
builder.append(failedTextLine);
builder.append("\n");
}
else {
break;
}
}
return new TestsOutput(false, builder.toString());
}
}
}
}
@@ -7,31 +7,27 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.core.EduUtils;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.StepicUser;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class Course {
@Expose
private List<Lesson> lessons = new ArrayList<Lesson>();
@Expose private List<Lesson> lessons = new ArrayList<Lesson>();
@Expose private List<StepicUser> authors = new ArrayList<StepicUser>();
@Expose private String description;
@Expose private String name;
private String myCourseDirectory = "";
@Expose private List<CourseInfo.Author> authors = new ArrayList<CourseInfo.Author>();
private boolean myUpToDate;
@Expose private String myCourseDirectory = "";
@Expose private int id;
@Expose private boolean myUpToDate;
@Expose private boolean isAdaptive;
@Expose @SerializedName("language") private String myLanguage = "Python";
@Expose @SerializedName("language")
private String myLanguage = "Python";
//this field is used to distinguish ordinary and CheckIO projects
//this field is used to distinguish ordinary and CheckIO projects,
//"PyCharm" is used here for historical reasons
private String courseType = EduNames.PYCHARM;
//this field is used to distinguish study and course creator modes
private String courseMode = EduNames.STUDY;
@Expose private String courseType = EduNames.PYCHARM;
@Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
/**
* Initializes state of course
@@ -72,20 +68,20 @@ public class Course {
}
@NotNull
public List<CourseInfo.Author> getAuthors() {
public List<StepicUser> getAuthors() {
return authors;
}
public static String getAuthorsString(@NotNull List<CourseInfo.Author> authors) {
public static String getAuthorsString(@NotNull List<StepicUser> authors) {
return StringUtil.join(authors, author -> author.getName(), ", ");
}
public void setAuthors(String[] authors) {
this.authors = new ArrayList<CourseInfo.Author>();
this.authors = new ArrayList<StepicUser>();
for (String name : authors) {
final List<String> pair = StringUtil.split(name, " ");
if (!pair.isEmpty())
this.authors.add(new CourseInfo.Author(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
this.authors.add(new StepicUser(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
}
}
@@ -133,7 +129,7 @@ public class Course {
myLanguage = language;
}
public void setAuthors(List<CourseInfo.Author> authors) {
public void setAuthors(List<StepicUser> authors) {
this.authors = authors;
}
@@ -146,6 +142,22 @@ 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;
}
@@ -11,25 +11,13 @@ import java.util.ArrayList;
import java.util.List;
public class Lesson implements StudyItem {
public int id;
@Transient
public List<Integer> steps;
@Transient
public List<String> tags;
@Transient
Boolean is_public;
@Expose
@SerializedName("title")
private String name;
@Expose
@SerializedName("task_list")
public List<Task> taskList = new ArrayList<Task>();
@Transient
private Course myCourse = null;
// index is visible to user number of lesson from 1 to lesson number
private int myIndex = -1;
@Expose public int id;
@Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number
@Expose @Transient public List<Integer> steps;
@Expose @SerializedName("title") private String name;
@Expose @SerializedName("task_list") public List<Task> taskList = new ArrayList<Task>();
@Transient public List<String> tags;
@Transient private Course myCourse = null;
public void initLesson(final Course course, boolean isRestarted) {
setCourse(course);
@@ -20,22 +20,13 @@ import java.util.Map;
* Implementation of task which contains task files, tests, input file for tests
*/
public class Task implements StudyItem {
@Expose
private String name;
// index is visible to user number of task from 1 to task number
private int myIndex;
private StudyStatus myStatus = StudyStatus.Uninitialized;
private int myStepicId;
@Expose
@SerializedName("task_files")
public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
private String text;
private Map<String, String> testsText = new HashMap<String, String>();
@Expose private String name;
@Expose private String text;
@Expose private int myStepicId;
@Expose private int myIndex; // index is visible to user number of task from 1 to task number
@Expose @SerializedName("task_files") public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
@Expose private Map<String, String> testsText = new HashMap<String, String>();
@Expose private StudyStatus myStatus = StudyStatus.Uninitialized;
@Transient private Lesson myLesson;
public Task() {}
@@ -18,19 +18,14 @@ import java.util.List;
*/
public class TaskFile {
@SerializedName("placeholders")
@Expose
private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
private int myIndex = -1;
@Expose
public String name;
@Expose
public String text;
@Expose public String name;
@Expose public String text;
@Expose private int myIndex = -1;
@Expose private boolean myUserCreated = false;
@Expose private boolean myTrackChanges = true;
@Expose private boolean myHighlightErrors = false;
@Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
@Transient private Task myTask;
private boolean myUserCreated = false;
private boolean myTrackChanges = true;
private boolean myHighlightErrors = false;
public void initTaskFile(final Task task, boolean isRestarted) {
setTask(task);
@@ -4,13 +4,13 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
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.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -106,30 +106,30 @@ public class StudyGenerator {
public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
@NotNull final Project project) {
try {
final List<Lesson> lessons = course.getLessons();
for (int i = 1; i <= lessons.size(); i++) {
Lesson lesson = lessons.get(i - 1);
lesson.setIndex(i);
createLesson(lesson, baseDir, resourceRoot, project);
}
baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
File[] files = resourceRoot.listFiles(
(dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
for (File file : files) {
File dir = new File(baseDir.getPath(), file.getName());
if (file.isDirectory()) {
FileUtil.copyDir(file, dir);
continue;
}
FileUtil.copy(file, dir);
}
}
catch (IOException e) {
LOG.error(e);
}
try {
final List<Lesson> lessons = course.getLessons();
for (int i = 1; i <= lessons.size(); i++) {
Lesson lesson = lessons.get(i - 1);
lesson.setIndex(i);
createLesson(lesson, baseDir, resourceRoot, project);
}
baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
File[] files = resourceRoot.listFiles(
(dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
for (File file : files) {
File dir = new File(baseDir.getPath(), file.getName());
if (file.isDirectory()) {
FileUtil.copyDir(file, dir);
continue;
}
FileUtil.copy(file, dir);
}
}
catch (IOException e) {
LOG.error(e);
}
}
}
@@ -8,10 +8,12 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbModePermission;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -22,38 +24,40 @@ 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.jetbrains.edu.learning.StudyProjectComponent;
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.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyProjectComponent;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.stepic.CourseInfo;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
public class StudyProjectGenerator {
private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
protected static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses");
private static final String CACHE_NAME = "courseNames.txt";
private List<CourseInfo> myCourses = new ArrayList<>();
protected CourseInfo mySelectedCourseInfo;
private static final String COURSE_NAME_ATTRIBUTE = "name";
private static final String COURSE_DESCRIPTION = "description";
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();
public StepicUser myUser;
private List<CourseInfo> myCourses = new ArrayList<>();
protected CourseInfo mySelectedCourseInfo;
public void setCourses(List<CourseInfo> courses) {
myCourses = courses;
@@ -64,48 +68,67 @@ public class StudyProjectGenerator {
}
public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
final Course course = getCourse();
StudyTaskManager.getInstance(project).setUser(myUser);
final Course course = getCourse(project);
if (course == null) {
LOG.warn("Course is null");
return;
}
final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
StudyTaskManager.getInstance(project).setCourse(course);
ApplicationManager.getApplication().invokeLater(
() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND,
() -> ApplicationManager.getApplication().runWriteAction(() -> {
course.initCourse(false);
final File courseDirectory = new File(ourCoursesDir, course.getName());
StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath());
course.setCourseDirectory(courseDirectory.getAbsolutePath());
VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
StudyProjectComponent.getInstance(project).registerStudyToolWindow(course);
openFirstTask(course, project);
})));
}
protected Course getCourse() {
Reader reader = null;
try {
final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
if (courseFile.exists()) {
reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final Course course = gson.fromJson(reader, Course.class);
course.initCourse(false);
return course;
@Nullable
protected 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()) {
return readCourseFromCache(courseFile, false);
}
else if (myUser != null) {
final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX +
mySelectedCourseInfo.getName() + "_" +
myUser.getEmail()), EduNames.COURSE_META_FILE);
if (adaptiveCourseFile.exists()) {
return readCourseFromCache(adaptiveCourseFile, true);
}
}
catch (FileNotFoundException | UnsupportedEncodingException e) {
LOG.error(e);
final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
if (course != null) {
flushCourse(project, course);
course.initCourse(false);
}
return course;
}
@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().create();
final Course course = gson.fromJson(reader, Course.class);
course.initCourse(isAdaptive);
return course;
}
catch (UnsupportedEncodingException e) {
LOG.warn(e.getMessage());
}
catch (FileNotFoundException e) {
LOG.warn(e.getMessage());
}
finally {
StudyUtils.closeSilently(reader);
}
final Course course = EduStepicConnector.getCourse(mySelectedCourseInfo);
if (course != null) {
flushCourse(course);
}
return course;
return null;
}
public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
@@ -131,7 +154,8 @@ public class StudyProjectGenerator {
final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
ProjectView.getInstance(project).select(file, activeVirtualFile, true);
FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
} else {
}
else {
String first = StudyUtils.getFirst(taskFiles.keySet());
if (first != null) {
NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
@@ -142,8 +166,8 @@ public class StudyProjectGenerator {
}
}
public void flushCourse(@NotNull final Course course) {
final File courseDirectory = new File(ourCoursesDir, course.getName());
public static void flushCourse(@NotNull final Project project, @NotNull final Course course) {
final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
FileUtil.createDirectory(courseDirectory);
flushCourseJson(course, courseDirectory);
@@ -208,7 +232,6 @@ public class StudyProjectGenerator {
else {
FileUtil.writeToFile(file, taskFile.text);
}
}
catch (IOException e) {
LOG.error("ERROR copying file " + name);
@@ -217,9 +240,12 @@ public class StudyProjectGenerator {
final Map<String, String> testsText = task.getTestsText();
for (Map.Entry<String, String> entry : testsText.entrySet()) {
final File testsFile = new File(taskDirectory, entry.getKey());
if (testsFile.exists()) {
FileUtil.delete(testsFile);
}
FileUtil.createIfDoesntExist(testsFile);
try {
FileUtil.writeToFile(testsFile, entry.getValue());
FileUtil.writeToFile(testsFile, entry.getValue());
}
catch (IOException e) {
LOG.error("ERROR copying tests file");
@@ -235,8 +261,8 @@ public class StudyProjectGenerator {
}
}
private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
final String json = gson.toJson(course);
final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
final FileOutputStream fileOutputStream;
@@ -269,7 +295,7 @@ public class StudyProjectGenerator {
*/
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public static void flushCache(List<CourseInfo> courses) {
File cacheFile = new File(ourCoursesDir, CACHE_NAME);
File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
PrintWriter writer = null;
try {
if (!createCacheFile(cacheFile)) return;
@@ -290,8 +316,8 @@ public class StudyProjectGenerator {
}
private static boolean createCacheFile(File cacheFile) throws IOException {
if (!ourCoursesDir.exists()) {
final boolean created = ourCoursesDir.mkdirs();
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;
@@ -307,12 +333,13 @@ public class StudyProjectGenerator {
return true;
}
public List<CourseInfo> getCourses(boolean force) {
if (ourCoursesDir.exists()) {
// Supposed to be called under progress
public List<CourseInfo> getCoursesAsynchronouslyIfNeeded(boolean force) {
if (OUR_COURSES_DIR.exists()) {
myCourses = getCoursesFromCache();
}
if (force || myCourses.isEmpty()) {
myCourses = EduStepicConnector.getCourses();
myCourses = execCancelable(EduStepicConnector::getCourses);
flushCache(myCourses);
}
if (myCourses.isEmpty()) {
@@ -321,6 +348,22 @@ public class StudyProjectGenerator {
return myCourses;
}
public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
try {
return ProgressManager.getInstance()
.runProcessWithProgressSynchronously(new ThrowableComputable<List<CourseInfo>, RuntimeException>() {
@Override
public List<CourseInfo> compute() throws RuntimeException {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
return getCoursesAsynchronouslyIfNeeded(force);
}
}, progressTitle, true, project);
}
catch (RuntimeException e) {
return Collections.singletonList(CourseInfo.INVALID_COURSE);
}
}
public void addSettingsStateListener(@NotNull SettingsListener listener) {
myListeners.add(listener);
}
@@ -336,7 +379,7 @@ public class StudyProjectGenerator {
}
public static List<CourseInfo> getBundledIntro() {
final File introCourse = new File(ourCoursesDir, "Introduction to Python");
final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
if (introCourse.exists()) {
final CourseInfo courseInfo = getCourseInfo(introCourse);
@@ -347,7 +390,7 @@ public class StudyProjectGenerator {
public static List<CourseInfo> getCoursesFromCache() {
List<CourseInfo> courses = new ArrayList<>();
final File cacheFile = new File(ourCoursesDir, CACHE_NAME);
final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
if (!cacheFile.exists()) {
return courses;
}
@@ -360,7 +403,9 @@ public class StudyProjectGenerator {
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);
if (!courseInfo.isAdaptive()) {
courses.add(courseInfo);
}
}
}
catch (IOException | JsonSyntaxException e) {
@@ -369,7 +414,8 @@ public class StudyProjectGenerator {
finally {
StudyUtils.closeSilently(reader);
}
} finally {
}
finally {
StudyUtils.closeSilently(inputStream);
}
}
@@ -378,6 +424,7 @@ public class StudyProjectGenerator {
}
return courses;
}
/**
* Adds course from zip archive to courses
*
@@ -389,12 +436,14 @@ public class StudyProjectGenerator {
try {
String fileName = file.getName();
String unzippedName = fileName.substring(0, fileName.indexOf("."));
File courseDir = new File(ourCoursesDir, unzippedName);
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)) {
courseDir.renameTo(new File(ourCoursesDir, courseName.getName()));
//noinspection ResultOfMethodCallIgnored
courseDir.renameTo(new File(OUR_COURSES_DIR, courseName.getName()));
//noinspection ResultOfMethodCallIgnored
courseDir.delete();
}
return courseName;
@@ -409,7 +458,6 @@ public class StudyProjectGenerator {
/**
* 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
@@ -431,6 +479,7 @@ public class StudyProjectGenerator {
}
return null;
}
/**
* Parses course json meta file and finds course name
*
@@ -462,10 +511,13 @@ public class StudyProjectGenerator {
courseInfo.setName(courseName);
courseInfo.setDescription(courseDescription);
courseInfo.setType("pycharm " + language);
final ArrayList<CourseInfo.Author> authors = new ArrayList<>();
final ArrayList<StepicUser> authors = new ArrayList<>();
for (JsonElement author : courseAuthors) {
final JsonObject authorAsJsonObject = author.getAsJsonObject();
authors.add(new CourseInfo.Author(authorAsJsonObject.get("first_name").getAsString(), authorAsJsonObject.get("last_name").getAsString()));
final StepicUser stepicUser = new StepicUser();
stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString());
stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString());
authors.add(stepicUser);
}
courseInfo.setAuthors(authors);
}
@@ -5,9 +5,9 @@ import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.core.EduDocumentListener;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -21,6 +21,11 @@ public class StudyEditor extends PsiAwareTextEditorImpl {
private final TaskFile myTaskFile;
private static final Map<Document, EduDocumentListener> myDocumentListeners = new HashMap<Document, EduDocumentListener>();
public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
super(project, file, TextEditorProvider.getInstance());
myTaskFile = StudyUtils.getTaskFile(project, file);
}
public TaskFile getTaskFile() {
return myTaskFile;
}
@@ -30,11 +35,6 @@ public class StudyEditor extends PsiAwareTextEditorImpl {
myDocumentListeners.put(document, listener);
}
public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
super(project, file, TextEditorProvider.getInstance());
myTaskFile = StudyUtils.getTaskFile(project, file);
}
public static void removeListener(Document document) {
final EduDocumentListener listener = myDocumentListeners.get(document);
if (listener != null) {
@@ -8,14 +8,14 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
static final private String EDITOR_TYPE_ID = "StudyEditor";
final private FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
public class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
public static final String EDITOR_TYPE_ID = "StudyEditor";
private final FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
@@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic;
import com.google.gson.annotations.SerializedName;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -12,29 +13,26 @@ import java.util.List;
* and when project is being created
*/
public class CourseInfo {
boolean is_public;
public List<Integer> sections;
@SerializedName("title")
private String myName;
@SerializedName("summary")
private String myDescription;
@SerializedName("course_format")
//course type in format "pycharm <language>"
private String myType = "pycharm Python";
public static CourseInfo INVALID_COURSE = new CourseInfo();
@SerializedName("title") private String myName;
int id;
boolean isAdaptive;
boolean isPublic;
List<Integer> sections;
List<Integer> instructors = new ArrayList<Integer>();
List<Author> myAuthors = new ArrayList<Author>();
int id;
public static CourseInfo INVALID_COURSE = new CourseInfo();
List<StepicUser> myAuthors = new ArrayList<>();
@SerializedName("summary") private String myDescription;
@SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm <language>"
@Nullable private String username;
public String getName() {
return myName;
}
@NotNull
public List<Author> getAuthors() {
public List<StepicUser> getAuthors() {
return myAuthors;
}
@@ -48,7 +46,7 @@ public class CourseInfo {
@Override
public String toString() {
return myName;
return getName();
}
@Override
@@ -57,17 +55,26 @@ public class CourseInfo {
if (o == null || getClass() != o.getClass()) return false;
CourseInfo that = (CourseInfo)o;
if (that.getName() == null || that.getDescription() == null) return false;
return that.getName().equals(myName)
return that.getName().equals(getName())
&& that.getDescription().equals(myDescription);
}
@Override
public int hashCode() {
int result = myName != null ? myName.hashCode() : 0;
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 static class Author {
int id;
String first_name = "";
@@ -93,18 +100,18 @@ public class CourseInfo {
myName = name;
}
public void setAuthors(List<Author> authors) {
public void setAuthors(List<StepicUser> authors) {
myAuthors = authors;
for (Author author : authors) {
for (StepicUser author : authors) {
if (author.id > 0) {
instructors.add(author.id);
}
}
}
public void addAuthor(Author author) {
public void addAuthor(StepicUser author) {
if (myAuthors == null) {
myAuthors = new ArrayList<Author>();
myAuthors = new ArrayList<>();
}
myAuthors.add(author);
}
@@ -116,4 +123,20 @@ public class CourseInfo {
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;
}
}
@@ -0,0 +1,511 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
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;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.checker.StudyExecutor;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.*;
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.ui.StudyToolWindow;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
public class EduAdaptiveStepicConnector {
public static final String PYTHON27 = "python27";
public static final String PYTHON3 = "python3";
private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
private static final String STEPIC_URL = "https://stepic.org/";
private static final String STEPIC_API_URL = STEPIC_URL + "api/";
private static final String RECOMMENDATIONS_URL = "recommendations";
private static final String CONTENT_TYPE_APPL_JSON = "application/json";
private static final String LESSON_URL = "lessons/";
private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions";
private static final String ATTEMPTS_URL = "attempts";
private static final String SUBMISSION_URL = "submissions";
private static final String ASSIGNMENT_URL = "/assignments";
private static final String VIEWS_URL = "/views";
private static final String UNITS_URL = "/units";
private static final String DEFAULT_TASKFILE_NAME = "code.py";
@Nullable
public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
try {
final CloseableHttpClient client = getHttpClient(project);
final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL)
.addParameter("course", String.valueOf(course.getId()))
.build();
final HttpGet request = new HttpGet(uri);
setHeaders(request, CONTENT_TYPE_APPL_JSON);
final CloseableHttpResponse response = client.execute(request);
final StatusLine statusLine = response.getStatusLine();
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
if (recomWrapper.recommendations.length != 0) {
final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
final String lessonId = recommendation.lesson;
final StepicWrappers.LessonContainer
lessonContainer = getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class);
if (lessonContainer.lessons.size() == 1) {
final Lesson realLesson = lessonContainer.lessons.get(0);
course.getLessons().get(0).id = Integer.parseInt(lessonId);
viewAllSteps(client, realLesson.id);
for (int stepId : realLesson.steps) {
final StepicWrappers.Step step = getStep(stepId);
if (step.name.equals("code")) {
return getTaskFromStep(project, stepId, step, realLesson.getName());
}
}
LOG.warn("Got a lesson without code part as a recommendation");
}
else {
LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
}
}
}
else {
throw new IOException("Stepic returned non 200 status code: " + responseString);
}
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
}
return null;
}
private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
final URI unitsUrl = new URIBuilder(UNITS_URL).addParameter("lesson", String.valueOf(lessonId)).build();
final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
if (unitContainer.units.size() != 1) {
LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
return;
}
final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL);
for (Integer step : unitContainer.units.get(0).assignments) {
builder.addParameter("ids[]", String.valueOf(step));
}
final URI assignmentUrl = builder.build();
final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
if (assignments.assignments.size() > 0) {
for (StepicWrappers.Assignment assignment : assignments.assignments) {
final HttpPost post = new HttpPost(STEPIC_API_URL + VIEWS_URL);
final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
setHeaders(post, CONTENT_TYPE_APPL_JSON);
final CloseableHttpResponse viewPostResult = client.execute(post);
if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
}
}
}
else {
LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
}
}
public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
@NotNull final String user, int reaction) {
final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL);
final String json = new Gson()
.toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
final CloseableHttpClient client = getHttpClient(project);
setHeaders(post, CONTENT_TYPE_APPL_JSON);
try {
final CloseableHttpResponse execute = client.execute(post);
return execute.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED;
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
return false;
}
public static void addNextRecommendedTask(@NotNull final Project project, int reaction) {
final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null && editor != null && editor.getTaskFile() != null) {
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
final boolean recommendationReaction =
user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id),
String.valueOf(user.id), reaction);
if (recommendationReaction) {
final Task task = getNextRecommendation(project, course);
if (task != null) {
final Lesson adaptive = course.getLessons().get(0);
final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
if (reaction == 0 || reaction == -1) {
unsolvedTask.setName(task.getName());
unsolvedTask.setStepicId(task.getStepicId());
unsolvedTask.setText(task.getText());
unsolvedTask.getTestsText().clear();
final Map<String, String> testsText = task.getTestsText();
for (String testName : testsText.keySet()) {
unsolvedTask.addTestsTexts(testName, testsText.get(testName));
}
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
if (taskFiles.size() == 1) {
final TaskFile taskFile = editor.getTaskFile();
taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
ApplicationManager.getApplication().invokeLater(() ->
ApplicationManager.getApplication().runWriteAction(() ->
editor.getEditor()
.getDocument()
.setText(
taskFiles.get(
DEFAULT_TASKFILE_NAME).text)));
}
else {
LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
}
final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size()));
StudyProjectGenerator.flushTask(task, taskDirectory);
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
if (lessonDir != null) {
createTestFiles(course, task, unsolvedTask, lessonDir);
}
final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
if (window != null) {
window.setTaskText(unsolvedTask.getText(), unsolvedTask.getTaskDir(project), project);
}
}
else {
adaptive.addTask(task);
task.setIndex(adaptive.getTaskList().size());
final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
if (lessonDir != null) {
ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
try {
StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
}));
}
final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
StudyProjectGenerator.flushLesson(lessonDirectory, adaptive);
StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
adaptive.initLesson(course, true);
}
}
ApplicationManager.getApplication().invokeLater(() -> {
VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
ProjectView.getInstance(project).refresh();
});
}
else {
LOG.warn("Recommendation reactions weren't posted");
}
}
}
private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
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());
File newResourceRoot = null;
if (taskDir != null) {
newResourceRoot = new File(resourceRoot, taskDir.getName());
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.copy(resourceFile, fileInProject);
}
}
}
}
else {
LOG.warn("Task directory is null");
}
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
}));
}
@NotNull
private static Task getTaskFromStep(Project project,
int lessonID,
@NotNull final StepicWrappers.Step step, @NotNull String name) {
final Task task = new Task();
task.setName(name);
task.setStepicId(lessonID);
task.setText(step.text);
task.setStatus(StudyStatus.Unchecked);
if (step.options.samples != null) {
final StringBuilder builder = new StringBuilder();
for (List<String> sample : step.options.samples) {
if (sample.size() == 2) {
builder.append("<b>Sample Input:</b><br>");
builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
builder.append("<br>");
builder.append("<b>Sample Output:</b><br>");
builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
builder.append("<br><br>");
}
}
task.setText(task.getText() + "<br>" + builder.toString());
}
if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
String builder = "<b>Memory limit</b>: " +
step.options.executionMemoryLimit + " Mb" +
"<br>" +
"<b>Time limit</b>: " +
step.options.executionTimeLimit + "s" +
"<br><br>";
task.setText(task.getText() + builder);
}
if (step.options.test != null) {
for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
task.addTestsTexts(wrapper.name, wrapper.text);
}
}
else {
if (step.options.samples != null) {
createTestFileFromSamples(task, step.options.samples);
}
}
task.taskFiles = new HashMap<String, TaskFile>(); // TODO: it looks like we don't need taskFiles as map anymore
if (step.options.files != null) {
for (TaskFile taskFile : step.options.files) {
task.taskFiles.put(taskFile.name, taskFile);
}
}
else {
final TaskFile taskFile = new TaskFile();
taskFile.name = "code";
final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
task.taskFiles.put("code.py", taskFile);
}
return task;
}
private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
@NotNull final Task task, @NotNull final Project project) {
if (codeTemplates != null) {
final String languageString = getLanguageString(task, project);
if (languageString != null) {
return codeTemplates.getTemplateForLanguage(languageString);
}
}
return null;
}
@Nullable
public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
int attemptId = -1;
try {
attemptId = getAttemptId(project, task, ATTEMPTS_URL);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
if (attemptId != -1) {
final Editor editor = StudyUtils.getSelectedEditor(project);
String language = getLanguageString(task, project);
if (editor != null && language != null) {
final CloseableHttpClient client = getHttpClient(project);
StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
final StepicUser user = StudyTaskManager.getInstance(project).getUser();
if (user != null) {
final int id = user.getId();
wrapper = getCheckResults(attemptId, id, client, wrapper);
if (wrapper.submissions.length == 1) {
final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
return Pair.create(isSolved, wrapper.submissions[0].hint);
}
else {
LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
}
}
else {
LOG.warn("User is null");
}
}
}
else {
LOG.warn("Got an incorrect attempt id: " + attemptId);
}
return Pair.create(false, "");
}
@Nullable
private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
final int attemptId,
@NotNull final String language,
@NotNull final String text) {
final CloseableHttpResponse response;
try {
final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, text);
final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL);
setHeaders(httpPost, CONTENT_TYPE_APPL_JSON);
try {
httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
}
catch (UnsupportedEncodingException e) {
LOG.warn(e.getMessage());
}
response = client.execute(httpPost);
return new Gson().fromJson(EntityUtils.toString(response.getEntity()), StepicWrappers.ResultSubmissionWrapper.class);
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
return null;
}
@NotNull
private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
int id,
CloseableHttpClient client,
StepicWrappers.ResultSubmissionWrapper wrapper) {
try {
while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
TimeUnit.MILLISECONDS.sleep(500);
final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL)
.addParameter("attempt", String.valueOf(attemptId))
.addParameter("order", "desc")
.addParameter("user", String.valueOf(id))
.build();
final HttpGet httpGet = new HttpGet(submissionURI);
setHeaders(httpGet, CONTENT_TYPE_APPL_JSON);
final CloseableHttpResponse httpResponse = client.execute(httpGet);
final String entity = EntityUtils.toString(httpResponse.getEntity());
wrapper = new Gson().fromJson(entity, StepicWrappers.ResultSubmissionWrapper.class);
}
}
catch (InterruptedException e) {
LOG.warn(e.getMessage());
}
catch (IOException e) {
LOG.warn(e.getMessage());
}
catch (URISyntaxException e) {
LOG.warn(e.getMessage());
}
return wrapper;
}
@Nullable
private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
final Language pythonLanguage = Language.findLanguageByID("Python");
if (pythonLanguage != null) {
final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
if (language != null) {
final String versionString = language.getVersionString();
if (versionString != null) {
final List<String> versionStringParts = StringUtil.split(versionString, " ");
if (versionStringParts.size() == 2) {
return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3;
}
}
}
else {
StudyUtils.showNoSdkNotification(task, project);
}
}
return null;
}
private static int getAttemptId(@NotNull final Project project, @NotNull Task task, @NotNull final String attempts) throws IOException {
final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
final HttpPost post = new HttpPost(STEPIC_API_URL + attempts);
post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
final CloseableHttpClient client = getHttpClient(project);
setHeaders(post, CONTENT_TYPE_APPL_JSON);
final CloseableHttpResponse httpResponse = client.execute(post);
final String entity = EntityUtils.toString(httpResponse.getEntity());
final StepicWrappers.AttemptContainer container =
new Gson().fromJson(entity, StepicWrappers.AttemptContainer.class);
return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
}
private static void createTestFileFromSamples(@NotNull final Task task,
@NotNull final List<List<String>> samples) {
String testText = "from test_helper import check_samples\n\n" +
"if __name__ == '__main__':\n" +
" check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
task.addTestsTexts("tests.py", testText);
}
}
@@ -3,7 +3,6 @@ package com.jetbrains.edu.learning.stepic;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
@@ -17,6 +16,7 @@ import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.util.net.ssl.CertificateManager;
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.Course;
@@ -24,10 +24,7 @@ import com.jetbrains.edu.learning.courseFormat.Lesson;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.cookie.Cookie;
@@ -40,7 +37,6 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -49,7 +45,6 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -57,9 +52,9 @@ import java.security.cert.X509Certificate;
import java.util.*;
public class EduStepicConnector {
private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
private static final String stepicUrl = "https://stepic.org/";
private static final String stepicApiUrl = stepicUrl + "api/";
private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
private static String ourCSRFToken = "";
private static CloseableHttpClient ourClient;
@@ -70,15 +65,21 @@ public class EduStepicConnector {
private EduStepicConnector() {
}
public static boolean login(@NotNull final String user, @NotNull final String password) {
public static StepicUser login(@NotNull final String username, @NotNull final String password) {
initializeClient();
return postCredentials(user, password);
if (postCredentials(username, password)) {
final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
return stepicUserWrapper.users.get(0);
}
}
return null;
}
@Nullable
public static AuthorWrapper getCurrentUser() {
public static StepicWrappers.AuthorWrapper getCurrentUser() {
try {
return getFromStepic("stepics/1", AuthorWrapper.class);
return getFromStepic("stepics/1", StepicWrappers.AuthorWrapper.class);
}
catch (IOException e) {
LOG.warn("Couldn't get author info");
@@ -90,7 +91,7 @@ public class EduStepicConnector {
final HttpPost userRequest = new HttpPost(stepicApiUrl + "users");
initializeClient();
setHeaders(userRequest, "application/json");
String requestBody = new Gson().toJson(new UserWrapper(user, password));
String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
@@ -109,38 +110,47 @@ public class EduStepicConnector {
return true;
}
private static void initializeClient() {
final HttpGet request = new HttpGet(stepicUrl);
request.addHeader(new BasicHeader("referer", "https://stepic.org"));
request.addHeader(new BasicHeader("content-type", "application/json"));
public static void initializeClient() {
if (ourClient == null) {
final HttpGet request = new HttpGet(stepicUrl);
request.addHeader(new BasicHeader("referer", "https://stepic.org"));
request.addHeader(new BasicHeader("content-type", "application/json"));
HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
ourCookieStore = new BasicCookieStore();
HttpClientBuilder builder =
HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
ourCookieStore = new BasicCookieStore();
try {
// Create a trust manager that does not validate certificate for this connection
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
try {
// Create a trust manager that does not validate certificate for this connection
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
ourClient.execute(request);
saveCSRFToken();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
catch (NoSuchAlgorithmException e) {
LOG.error(e.getMessage());
}
catch (KeyManagementException e) {
LOG.error(e.getMessage());
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
ourClient.execute(request);
saveCSRFToken();
}
catch (IOException e) {
LOG.error(e.getMessage());
}
catch (NoSuchAlgorithmException e) {
LOG.error(e.getMessage());
}
catch (KeyManagementException e) {
LOG.error(e.getMessage());
}
}
}
@@ -164,14 +174,7 @@ public class EduStepicConnector {
nvps.add(new BasicNameValuePair("password", password));
nvps.add(new BasicNameValuePair("remember", "on"));
try {
request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
}
catch (UnsupportedEncodingException e) {
LOG.error(e.getMessage());
ourClient = null;
return false;
}
request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
setHeaders(request, "application/x-www-form-urlencoded");
@@ -182,7 +185,8 @@ public class EduStepicConnector {
if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
final HttpEntity responseEntity = response.getEntity();
final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
LOG.error("Failed to login " + responseString);
LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
LOG.debug("Failed to login " + responseString);
ourClient = null;
return false;
}
@@ -195,7 +199,7 @@ public class EduStepicConnector {
return true;
}
private static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
final HttpGet request = new HttpGet(stepicApiUrl + link);
if (ourClient == null) {
initializeClient();
@@ -212,6 +216,15 @@ public class EduStepicConnector {
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
return gson.fromJson(responseString, container);
}
@NotNull
public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
if (ourClient == null) {
login(project);
initializeClient();
}
return ourClient;
}
@NotNull
public static List<CourseInfo> getCourses() {
@@ -226,21 +239,21 @@ public class EduStepicConnector {
catch (IOException e) {
LOG.error("Cannot load course list " + e.getMessage());
}
return Collections.emptyList();
return Collections.singletonList(CourseInfo.INVALID_COURSE);
}
private static boolean addCoursesFromStepic(List<CourseInfo> result, int pageNumber) throws IOException {
final String url = pageNumber == 0 ? "courses" : "courses?page=" + String.valueOf(pageNumber);
final CoursesContainer coursesContainer = getFromStepic(url, CoursesContainer.class);
final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
final List<CourseInfo> courseInfos = coursesContainer.courses;
for (CourseInfo info : courseInfos) {
final String courseType = info.getType();
if (StringUtil.isEmptyOrSpaces(courseType)) continue;
if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue;
final List<String> typeLanguage = StringUtil.split(courseType, " ");
if (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0))) {
// TODO: should adaptive course be of PyCharmType ?
if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
for (Integer instructor : info.instructors) {
final CourseInfo.Author author = getFromStepic("users/" + String.valueOf(instructor), AuthorWrapper.class).users.get(0);
final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0);
info.addAuthor(author);
}
@@ -250,34 +263,55 @@ public class EduStepicConnector {
return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
}
public static Course getCourse(@NotNull final CourseInfo info) {
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.setName(info.getName());
String courseType = info.getType();
course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
course.setAdaptive(info.isAdaptive());
course.setId(info.id);
course.setUpToDate(true); // TODO: get from stepic
try {
for (Integer section : info.sections) {
course.addLessons(getLessons(section));
if (!course.isAdaptive()) {
String courseType = info.getType();
course.setName(info.getName());
course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
try {
for (Integer section : info.sections) {
course.addLessons(getLessons(section));
}
return course;
}
catch (IOException e) {
LOG.error("IOException " + e.getMessage());
}
return course;
}
catch (IOException e) {
LOG.error("IOException " + e.getMessage());
else {
final Lesson lesson = new Lesson();
course.setName(info.getName());
//TODO: more specific name?
lesson.setName("Adaptive");
course.addLesson(lesson);
final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
if (recommendation != null) {
lesson.addTask(recommendation);
}
return course;
}
return null;
}
public static List<Lesson> getLessons(int sectionId) throws IOException {
final SectionContainer sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), SectionContainer.class);
final StepicWrappers.SectionContainer
sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
List<Integer> unitIds = sectionContainer.sections.get(0).units;
final List<Lesson> lessons = new ArrayList<Lesson>();
for (Integer unitId : unitIds) {
UnitContainer unit = getFromStepic("units/" + String.valueOf(unitId), UnitContainer.class);
StepicWrappers.UnitContainer
unit = getFromStepic("units/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
int lessonID = unit.units.get(0).lesson;
LessonContainer lesson = getFromStepic("lessons/" + String.valueOf(lessonID), LessonContainer.class);
StepicWrappers.LessonContainer
lesson = getFromStepic("lessons/" + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
Lesson realLesson = lesson.lessons.get(0);
realLesson.taskList = new ArrayList<Task>();
for (Integer s : realLesson.steps) {
@@ -291,13 +325,13 @@ public class EduStepicConnector {
}
private static void createTask(Lesson lesson, Integer stepicId) throws IOException {
final Step step = getStep(stepicId);
final StepicWrappers.Step step = getStep(stepicId);
if (!step.name.equals(PYCHARM_PREFIX)) return;
final Task task = new Task();
task.setStepicId(stepicId);
task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX);
task.setText(step.text);
for (TestFileWrapper wrapper : step.options.test) {
for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
task.addTestsTexts(wrapper.name, wrapper.text);
}
@@ -310,8 +344,8 @@ public class EduStepicConnector {
lesson.taskList.add(task);
}
public static Step getStep(Integer step) throws IOException {
return getFromStepic("steps/" + String.valueOf(step), StepContainer.class).steps.get(0).block;
public static StepicWrappers.Step getStep(Integer step) throws IOException {
return getFromStepic("steps/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block;
}
@@ -334,14 +368,13 @@ public class EduStepicConnector {
return;
}
else {
final boolean success = login(login, password);
if (!success) return;
if (login(login, password) == null) return;
}
}
final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts");
setHeaders(attemptRequest, "application/json");
String attemptRequestBody = new Gson().toJson(new AttemptWrapper(task.getStepicId()));
String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
try {
@@ -352,12 +385,12 @@ public class EduStepicConnector {
if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to make attempt " + attemptResponseString);
}
final AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, AttemptContainer.class).attempts.get(0);
final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0);
final Map<String, TaskFile> taskFiles = task.getTaskFiles();
final ArrayList<SolutionFile> files = new ArrayList<SolutionFile>();
final ArrayList<StepicWrappers.SolutionFile> files = new ArrayList<StepicWrappers.SolutionFile>();
for (TaskFile fileEntry : taskFiles.values()) {
files.add(new SolutionFile(fileEntry.name, fileEntry.text));
files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
}
postSubmission(passed, attempt, files);
}
@@ -366,11 +399,11 @@ public class EduStepicConnector {
}
}
private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList<SolutionFile> files) throws IOException {
private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
final HttpPost request = new HttpPost(stepicApiUrl + "submissions");
setHeaders(request, "application/json");
String requestBody = new Gson().toJson(new SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
final CloseableHttpResponse response = ourClient.execute(request);
final HttpEntity responseEntity = response.getEntity();
@@ -398,15 +431,15 @@ public class EduStepicConnector {
indicator.setText("Uploading course to " + stepicUrl);
final HttpPost request = new HttpPost(stepicApiUrl + "courses");
if (ourClient == null || !relogin) {
if (!login()) return;
if (!login(project)) return;
}
final AuthorWrapper user = getCurrentUser();
final StepicWrappers.AuthorWrapper user = getCurrentUser();
if (user != null) {
course.setAuthors(user.users);
}
setHeaders(request, "application/json");
String requestBody = new Gson().toJson(new CourseWrapper(course));
String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
@@ -416,13 +449,13 @@ public class EduStepicConnector {
final StatusLine line = response.getStatusLine();
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
if (!relogin) {
login();
login(project);
postCourse(project, course, true, indicator);
}
LOG.error("Failed to push " + responseString);
return;
}
final CourseInfo postedCourse = new Gson().fromJson(responseString, CoursesContainer.class).courses.get(0);
final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
int position = 1;
@@ -439,14 +472,13 @@ public class EduStepicConnector {
}
}
private static boolean login() {
final String login = StudySettings.getInstance().getLogin();
private static boolean login(@NotNull final Project project) {
final String login = StudyTaskManager.getInstance(project).getLogin();
if (StringUtil.isEmptyOrSpaces(login)) {
return showLoginDialog();
}
else {
boolean success = login(login, StudySettings.getInstance().getPassword());
if (!success) {
if (login(login, StudyTaskManager.getInstance(project).getPassword()) == null) {
return showLoginDialog();
}
}
@@ -498,8 +530,8 @@ public class EduStepicConnector {
private static void postUnit(int lessonId, int position, int sectionId) {
final HttpPost request = new HttpPost(stepicApiUrl + "units");
setHeaders(request, "application/json");
final UnitWrapper unitWrapper = new UnitWrapper();
unitWrapper.unit = new Unit();
final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
unitWrapper.unit = new StepicWrappers.Unit();
unitWrapper.unit.lesson = lessonId;
unitWrapper.unit.position = position;
unitWrapper.unit.section = sectionId;
@@ -524,11 +556,11 @@ public class EduStepicConnector {
private static int postModule(int courseId, int position, @NotNull final String title) {
final HttpPost request = new HttpPost(stepicApiUrl + "sections");
setHeaders(request, "application/json");
final Section section = new Section();
final StepicWrappers.Section section = new StepicWrappers.Section();
section.course = courseId;
section.title = title;
section.position = position;
final SectionWrapper sectionContainer = new SectionWrapper();
final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
sectionContainer.section = section;
String requestBody = new Gson().toJson(sectionContainer);
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
@@ -541,7 +573,8 @@ public class EduStepicConnector {
if (line.getStatusCode() != HttpStatus.SC_CREATED) {
LOG.error("Failed to push " + responseString);
}
final Section postedSection = new Gson().fromJson(responseString, SectionContainer.class).sections.get(0);
final StepicWrappers.Section
postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
return postedSection.id;
}
catch (IOException e) {
@@ -550,17 +583,17 @@ public class EduStepicConnector {
return -1;
}
public static int updateLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPut request = new HttpPut(stepicApiUrl + "lessons/" + String.valueOf(lesson.id));
if (ourClient == null) {
if (!login()) {
if (!login(project)) {
LOG.error("Failed to push lesson");
return 0;
}
}
setHeaders(request, "application/json");
String requestBody = new Gson().toJson(new LessonWrapper(lesson));
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
@@ -589,14 +622,14 @@ public class EduStepicConnector {
return -1;
}
public static int postLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
final HttpPost request = new HttpPost(stepicApiUrl + "lessons");
if (ourClient == null) {
login();
login(project);
}
setHeaders(request, "application/json");
String requestBody = new Gson().toJson(new LessonWrapper(lesson));
String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
@@ -646,7 +679,7 @@ public class EduStepicConnector {
setHeaders(request, "application/json");
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
ApplicationManager.getApplication().invokeLater(() -> {
final String requestBody = gson.toJson(new StepSourceWrapper(project, task, lessonId));
final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
try {
@@ -664,274 +697,9 @@ public class EduStepicConnector {
});
}
private static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
request.addHeader(new BasicHeader("referer", stepicUrl));
request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
request.addHeader(new BasicHeader("content-type", contentType));
}
private static class StepContainer {
List<StepSource> steps;
}
private static class Step {
@Expose StepOptions options;
@Expose String text;
@Expose String name = "pycharm";
@Expose StepOptions source;
public static Step fromTask(Project project, @NotNull final Task task) {
final Step step = new Step();
step.text = task.getTaskText(project);
step.source = StepOptions.fromTask(project, task);
return step;
}
}
private static class StepOptions {
@Expose List<TestFileWrapper> test;
@Expose String title;
@Expose List<TaskFile> files;
@Expose String text;
public static StepOptions fromTask(final Project project, @NotNull final Task task) {
final StepOptions source = new StepOptions();
setTests(task, source, project);
source.files = new ArrayList<TaskFile>();
source.title = task.getName();
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
final TaskFile taskFile = new TaskFile();
TaskFile.copy(entry.getValue(), taskFile);
ApplicationManager.getApplication().runWriteAction(() -> {
final VirtualFile taskDir = task.getTaskDir(project);
assert taskDir != null;
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
assert ideaDir != null;
EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
});
taskFile.name = entry.getKey();
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
if (ideaDir == null) return null;
final VirtualFile file = ideaDir.findChild(taskFile.name);
try {
if (file != null) {
if (EduUtils.isImage(taskFile.name)) {
taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
}
else {
taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
}
}
}
catch (IOException e) {
LOG.error("Can't find file " + file.getPath());
}
source.files.add(taskFile);
}
return source;
}
private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
final Map<String, String> testsText = task.getTestsText();
if (testsText.isEmpty()) {
ApplicationManager.getApplication().runReadAction(() -> {
source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
});
}
else {
source.test = new ArrayList<TestFileWrapper>();
for (Map.Entry<String, String> entry : testsText.entrySet()) {
source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
}
}
}
}
private static class CoursesContainer {
public List<CourseInfo> courses;
public Map meta;
}
static class StepSourceWrapper {
@Expose
StepSource stepSource;
public StepSourceWrapper(Project project, Task task, int lessonId) {
stepSource = new StepSource(project, task, lessonId);
}
}
static class CourseWrapper {
CourseInfo course;
public CourseWrapper(Course course) {
this.course = new CourseInfo();
this.course.setName(course.getName());
this.course.setDescription(course.getDescription());
this.course.setAuthors(course.getAuthors());
}
}
static class LessonWrapper {
Lesson lesson;
public LessonWrapper(Lesson lesson) {
this.lesson = new Lesson();
this.lesson.setName(lesson.getName());
this.lesson.id = lesson.id;
this.lesson.steps = new ArrayList<Integer>();
}
}
static class LessonContainer {
List<Lesson> lessons;
}
static class StepSource {
@Expose Step block;
@Expose int position = 0;
@Expose int lesson = 0;
public StepSource(Project project, Task task, int lesson) {
this.lesson = lesson;
position = task.getIndex();
block = Step.fromTask(project, task);
}
}
static class TestFileWrapper {
@Expose private final String name;
@Expose private final String text;
public TestFileWrapper(String name, String text) {
this.name = name;
this.text = text;
}
}
static class Section {
List<Integer> units;
int course;
String title;
int position;
int id;
}
static class SectionWrapper {
Section section;
}
static class SectionContainer {
List<Section> sections;
List<Lesson> lessons;
List<Unit> units;
}
static class Unit {
int id;
int section;
int lesson;
int position;
}
static class UnitContainer {
List<Unit> units;
}
static class UnitWrapper{
Unit unit;
}
static class AttemptWrapper {
static class Attempt {
public Attempt(int step) {
this.step = step;
}
int step;
int id;
}
public AttemptWrapper(int step) {
attempt = new Attempt(step);
}
Attempt attempt;
}
static class AttemptContainer {
List<AttemptWrapper.Attempt> attempts;
}
static class SolutionFile {
String name;
String text;
public SolutionFile(String name, String text) {
this.name = name;
this.text = text;
}
}
static class AuthorWrapper {
List<CourseInfo.Author> users;
}
static class SubmissionWrapper {
Submission submission;
public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
submission = new Submission(score, attempt, files);
}
static class Submission {
int attempt;
private final Reply reply;
public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
reply = new Reply(files, score);
this.attempt = attempt;
}
static class Reply {
String score;
List<SolutionFile> solution;
public Reply(ArrayList<SolutionFile> files, String score) {
this.score = score;
solution = files;
}
}
}
}
static class User {
String first_name;
String last_name;
String email;
String password;
public User(String user, String password) {
email = user;
this.password = password;
this.first_name = user;
this.last_name = user;
}
}
static class UserWrapper {
User user;
public UserWrapper(String user, String password) {
this.user = new User(user, password);
}
}
}
@@ -14,6 +14,7 @@ public class LoginDialog extends DialogWrapper {
myLoginPanel = new LoginPanel(this);
setTitle("Login to Stepic");
setOKButtonText("Login");
setTitle("Login to Stepic");
init();
}
@@ -40,14 +41,12 @@ public class LoginDialog extends DialogWrapper {
@Override
protected void doOKAction() {
AuthDataHolder authData = myLoginPanel.getAuthData();
final boolean success = EduStepicConnector.login(authData.email, authData.password);
if (!success) {
setErrorText("Login failed");
final StepicUser user = EduStepicConnector.login(authData.email, authData.password);
if (user != null) {
super.doOKAction();
}
else {
StudySettings.getInstance().setLogin(authData.email);
StudySettings.getInstance().setPassword(authData.password);
super.doOKAction();
setErrorText("Login failed");
}
}
@@ -0,0 +1,196 @@
package com.jetbrains.edu.learning.stepic;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class StepicAdaptiveReactionsPanel extends JPanel {
private final ReactionButtonPanel myHardPanel;
private final ReactionButtonPanel myBoringPanel;
private final Project myProject;
private static final int TOO_HARD_REACTION = 0;
private static final int TOO_BORING_REACTION = -1;
private static final String HARD_REACTION = "Too Hard";
private static final String BORING_REACTION = "Too Boring";
private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved";
private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task";
private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task";
public StepicAdaptiveReactionsPanel(@NotNull final Project project) {
myProject = project;
setLayout(new GridBagLayout());
setBackground(UIUtil.getTextFieldBackground());
myHardPanel = new ReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_HARD_REACTION);
myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_BORING_REACTION);
addFileListener();
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
add(Box.createVerticalStrut(3), c);
c.gridx = 1;
c.gridy = 1;
add(Box.createHorizontalStrut(3), c);
c.weightx = 1;
c.gridx = 2;
add(myHardPanel, c);
c.gridx = 3;
c.weightx = 0;
add(Box.createHorizontalStrut(3), c);
c.weightx = 1;
c.gridx = 4;
add(myBoringPanel, c);
c.gridx = 5;
c.weightx = 0;
add(Box.createHorizontalStrut(3), c);
}
public void setEnabledRecursive(final boolean isEnabled) {
ApplicationManager.getApplication().invokeLater(() -> {
super.setEnabled(isEnabled);
myHardPanel.setEnabledRecursive(isEnabled);
myBoringPanel.setEnabledRecursive(isEnabled);
});
}
private void addFileListener() {
final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() {
@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
}
@Override
public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
}
@Override
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
}
};
myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener);
}
private class ReactionButtonPanel extends JPanel {
private final JPanel myButtonPanel;
private final JLabel myLabel;
public ReactionButtonPanel(@NotNull final String text,
@NotNull final String enabledTooltip,
@NotNull final String disabledTooltip,
int reaction) {
com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
myLabel = new JLabel(text);
myButtonPanel = new JPanel();
myButtonPanel.setLayout(new BoxLayout(myButtonPanel, BoxLayout.PAGE_AXIS));
myButtonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip);
myButtonPanel.add(Box.createVerticalStrut(5));
myButtonPanel.add(myLabel);
myButtonPanel.add(Box.createVerticalStrut(5));
setEnabledRecursive(isEnabled);
setLayout(new GridBagLayout());
setBorder(BorderFactory.createEtchedBorder());
add(myButtonPanel);
addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, reaction));
}
private void addMouseListener(@NotNull Runnable onClickAction) {
final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(onClickAction, this);
this.addMouseListener(mouseAdapter);
myButtonPanel.addMouseListener(mouseAdapter);
myLabel.addMouseListener(mouseAdapter);
}
public void setEnabledRecursive(final boolean isEnabled) {
ApplicationManager.getApplication().invokeLater(() -> {
super.setEnabled(isEnabled);
myButtonPanel.setEnabled(isEnabled);
myLabel.setEnabled(isEnabled);
});
}
private class ReactionMouseAdapter extends MouseAdapter {
private final Runnable myClickAction;
private final ReactionButtonPanel myPanel;
public ReactionMouseAdapter(@NotNull final Runnable onClickAction, @NotNull final ReactionButtonPanel mainPanel) {
myClickAction = onClickAction;
myPanel = mainPanel;
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
if (task != null && task.getStatus() != StudyStatus.Solved) {
final ProgressIndicatorBase progress = new ProgressIndicatorBase();
progress.setText("Loading Next Recommendation");
ProgressManager.getInstance().run(new Task.Backgroundable(myProject,
"Loading Next Recommendation") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false);
myClickAction.run();
}
@Override
protected void onFinished() {
StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true);
}
});
}
}
}
@Override
public void mouseEntered(MouseEvent e) {
final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) {
setBackground(UIUtil.getButtonSelectColor());
}
}
@Override
public void mouseExited(MouseEvent e) {
setBackground(UIUtil.getLabelBackground());
}
private void setBackground(Color color) {
myPanel.setBackground(color);
myButtonPanel.setBackground(color);
}
}
}
}
@@ -15,8 +15,12 @@
*/
package com.jetbrains.edu.learning.stepic;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.settings.StudyOptionsProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -31,6 +35,7 @@ import java.awt.event.FocusListener;
public class StepicStudyOptions implements StudyOptionsProvider {
private static final String DEFAULT_PASSWORD_TEXT = "************";
private static final Logger LOG = Logger.getInstance(StepicStudyOptions.class);
private JTextField myLoginTextField;
private JPasswordField myPasswordField;
private JPanel myPane;
@@ -98,13 +103,19 @@ public class StepicStudyOptions implements StudyOptionsProvider {
myPasswordField.setText(StringUtil.isEmpty(password) ? null : password);
}
@Override
public void reset() {
final StudySettings studySettings = StudySettings.getInstance();
setLogin(studySettings.getLogin());
setPassword(DEFAULT_PASSWORD_TEXT);
Project project = StudyUtils.getStudyProject();
if (project != null) {
final StudyTaskManager studySettings = StudyTaskManager.getInstance(project);
setLogin(studySettings.getLogin());
setPassword(DEFAULT_PASSWORD_TEXT);
resetCredentialsModification();
resetCredentialsModification();
}
else {
LOG.warn("No study object is opened");
}
}
@Override
@@ -112,13 +123,20 @@ public class StepicStudyOptions implements StudyOptionsProvider {
}
@Override
public void apply() {
if (myCredentialsModified) {
final StudySettings studySettings = StudySettings.getInstance();
studySettings.setLogin(getLogin());
studySettings.setPassword(getPassword());
if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
EduStepicConnector.login(getLogin(), getPassword());
final Project project = StudyUtils.getStudyProject();
if (project != null) {
final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
studyTaskManager.setLogin(getLogin());
studyTaskManager.setPassword(getPassword());
if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) {
EduStepicConnector.login(getLogin(), getPassword());
}
}
else {
LOG.warn("No study object is opened");
}
}
resetCredentialsModification();
@@ -0,0 +1,85 @@
package com.jetbrains.edu.learning.stepic;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.edu.learning.StudyTaskManager;
public class StepicUser {
private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
private static final Logger LOG = Logger.getInstance(StepicUser.class);
int id;
String firstName;
String last_name;
String email;
public StepicUser() {
}
public StepicUser(String email, String password) {
this.email = email;
setPassword(password);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return last_name;
}
public void setLastName(String last_name) {
this.last_name = last_name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
final String login = getEmail();
if (StringUtil.isEmptyOrSpaces(login)) return "";
String password;
try {
password = PasswordSafe.getInstance().getPassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
password = "";
}
return StringUtil.notNullize(password);
}
public void setPassword(String password) {
try {
PasswordSafe.getInstance().storePassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + getEmail() + "]", e);
}
}
public String getName() {
return StringUtil.join(new String[]{firstName, last_name}, " ");
}
}
@@ -0,0 +1,421 @@
package com.jetbrains.edu.learning.stepic;
import com.google.gson.annotations.Expose;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
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.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class StepicWrappers {
private static final Logger LOG = Logger.getInstance(StepOptions.class);
static class StepContainer {
List<StepSource> steps;
}
public static class Step {
@Expose StepOptions options;
@Expose String text;
@Expose String name = "pycharm";
@Expose StepOptions source;
public static Step fromTask(Project project, @NotNull final Task task) {
final Step step = new Step();
step.text = task.getTaskText(project);
step.source = StepOptions.fromTask(project, task);
return step;
}
}
public static class StepOptions {
@Expose List<TestFileWrapper> test;
@Expose String title;
@Expose List<TaskFile> files;
@Expose String text;
@Expose List<List<String>> samples;
@Expose Integer executionMemoryLimit;
@Expose Integer executionTimeLimit;
@Expose CodeTemplatesWrapper codeTemplates;
public static StepOptions fromTask(final Project project, @NotNull final Task task) {
final StepOptions source = new StepOptions();
setTests(task, source, project);
source.files = new ArrayList<TaskFile>();
source.title = task.getName();
for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
final TaskFile taskFile = new TaskFile();
TaskFile.copy(entry.getValue(), taskFile);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final VirtualFile taskDir = task.getTaskDir(project);
assert taskDir != null;
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
assert ideaDir != null;
EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
}
});
taskFile.name = entry.getKey();
VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
if (ideaDir == null) return null;
final VirtualFile file = ideaDir.findChild(taskFile.name);
try {
if (file != null) {
if (EduUtils.isImage(taskFile.name)) {
taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
}
else {
taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
}
}
}
catch (IOException e) {
LOG.error("Can't find file " + file.getPath());
}
source.files.add(taskFile);
}
return source;
}
private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
final Map<String, String> testsText = task.getTestsText();
if (testsText.isEmpty()) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
}
});
}
else {
source.test = new ArrayList<TestFileWrapper>();
for (Map.Entry<String, String> entry : testsText.entrySet()) {
source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
}
}
}
}
static class CodeTemplatesWrapper {
String python3;
String python27;
@Nullable
public String getTemplateForLanguage(@NotNull final String langauge) {
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON27)) {
return python27;
}
if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) {
return python3;
}
return null;
}
}
static class CoursesContainer {
public List<CourseInfo> courses;
public Map meta;
}
static class StepSourceWrapper {
@Expose
StepSource stepSource;
public StepSourceWrapper(Project project, Task task, int lessonId) {
stepSource = new StepSource(project, task, lessonId);
}
}
static class CourseWrapper {
CourseInfo course;
public CourseWrapper(Course course) {
this.course = new CourseInfo();
this.course.setName(course.getName());
this.course.setDescription(course.getDescription());
this.course.setAuthors(course.getAuthors());
}
}
static class LessonWrapper {
Lesson lesson;
public LessonWrapper(Lesson lesson) {
this.lesson = new Lesson();
this.lesson.setName(lesson.getName());
this.lesson.id = lesson.id;
this.lesson.steps = new ArrayList<Integer>();
}
}
static class LessonContainer {
List<Lesson> lessons;
}
static class StepSource {
@Expose Step block;
@Expose int position = 0;
@Expose int lesson = 0;
public StepSource(Project project, Task task, int lesson) {
this.lesson = lesson;
position = task.getIndex();
block = Step.fromTask(project, task);
}
}
static class TestFileWrapper {
@Expose public final String name;
@Expose public final String text;
public TestFileWrapper(String name, String text) {
this.name = name;
this.text = text;
}
}
static class Section {
List<Integer> units;
int course;
String title;
int position;
int id;
}
static class SectionWrapper {
Section section;
}
static class SectionContainer {
List<Section> sections;
List<Lesson> lessons;
List<Unit> units;
}
static class Unit {
int id;
int section;
int lesson;
int position;
List<Integer> assignments;
}
static class UnitContainer {
List<Unit> units;
}
static class UnitWrapper {
Unit unit;
}
static class AttemptWrapper {
static class Attempt {
public Attempt(int step) {
this.step = step;
}
int step;
int id;
}
public AttemptWrapper(int step) {
attempt = new Attempt(step);
}
Attempt attempt;
}
static class AttemptToPostWrapper {
static class Attempt {
int step;
String dataset_url;
String status;
String time;
String time_left;
String user;
String user_id;
public Attempt(int step) {
this.step = step;
}
}
public AttemptToPostWrapper(int step) {
attempt = new Attempt(step);
}
Attempt attempt;
}
static class AttemptContainer {
List<AttemptWrapper.Attempt> attempts;
}
static class SolutionFile {
String name;
String text;
public SolutionFile(String name, String text) {
this.name = name;
this.text = text;
}
}
static class AuthorWrapper {
List<StepicUser> users;
}
static class SubmissionWrapper {
Submission submission;
public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
submission = new Submission(score, attempt, files);
}
static class Submission {
int attempt;
private final Reply reply;
public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
reply = new Reply(files, score);
this.attempt = attempt;
}
static class Reply {
String score;
List<SolutionFile> solution;
public Reply(ArrayList<SolutionFile> files, String score) {
this.score = score;
solution = files;
}
}
}
}
static class UserWrapper {
StepicUser user;
public UserWrapper(String user, String password) {
this.user = new StepicUser(user, password);
}
}
static class RecommendationReaction {
int reaction;
String user;
String lesson;
public RecommendationReaction(int reaction, String user, String lesson) {
this.reaction = reaction;
this.user = user;
this.lesson = lesson;
}
}
static class RecommendationReactionWrapper {
RecommendationReaction recommendationReaction;
public RecommendationReactionWrapper(RecommendationReaction recommendationReaction) {
this.recommendationReaction = recommendationReaction;
}
}
static class RecommendationWrapper {
Recommendation[] recommendations;
}
static class Recommendation {
String id;
String lesson;
}
static class SubmissionToPostWrapper {
Submission submission;
public SubmissionToPostWrapper(@NotNull String attemptId, @NotNull String language, @NotNull String code) {
submission = new Submission(attemptId, new Submission.Reply(language, code));
}
static class Submission {
String attempt;
Reply reply;
public Submission(String attempt, Reply reply) {
this.attempt = attempt;
this.reply = reply;
}
static class Reply {
String language;
String code;
public Reply(String language, String code) {
this.language = language;
this.code = code;
}
}
}
}
static class ResultSubmissionWrapper {
ResultSubmission[] submissions;
static class ResultSubmission {
int id;
String status;
String hint;
}
}
static class AssignmentsWrapper {
List<Assignment> assignments;
}
static class Assignment {
int id;
int step;
}
static class ViewsWrapper {
View view;
public ViewsWrapper(final int assignment, final int step) {
this.view = new View(assignment, step);
}
}
static class View {
int assignment;
int step;
public View(int assignment, int step) {
this.assignment = assignment;
this.step = step;
}
}
}
@@ -1,90 +0,0 @@
/*
* Copyright 2000-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.edu.learning.stepic;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("MethodMayBeStatic")
@State(name = "StudySettings", storages = @Storage("stepic_settings.xml"))
public class StudySettings implements PersistentStateComponent<StudySettings.State> {
private State myState = new State();
public static class State {
@Nullable public String LOGIN = null;
}
private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
private static final Logger LOG = Logger.getInstance(StudySettings.class.getName());
public static StudySettings getInstance() {
return ServiceManager.getService(StudySettings.class);
}
@NotNull
public String getPassword() {
final String login = getLogin();
if (StringUtil.isEmptyOrSpaces(login)) return "";
String password;
try {
password = PasswordSafe.getInstance().getPassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
password = "";
}
return StringUtil.notNullize(password);
}
public void setPassword(@NotNull String password) {
try {
PasswordSafe.getInstance().storePassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY, password);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
}
}
@Nullable
public String getLogin() {
return myState.LOGIN;
}
public void setLogin(@Nullable String login) {
myState.LOGIN = login;
}
@Nullable
@Override
public StudySettings.State getState() {
return myState;
}
@Override
public void loadState(StudySettings.State state) {
myState = state;
}
}
@@ -40,6 +40,10 @@ public class StudyAddRemoteCourse {
return myContentPanel;
}
public JTextField getLoginField() {
return myLoginField;
}
public String getPassword() {
return String.valueOf(myPasswordField.getPassword());
}
@@ -35,7 +35,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
class StudyBrowserWindow extends JFrame {
public class StudyBrowserWindow extends JFrame {
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
private static final String EVENT_TYPE_CLICK = "click";
private JFXPanel myPanel;
@@ -18,6 +18,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.jetbrains.edu.learning.StudyPluginConfigurator;
import com.jetbrains.edu.learning.StudyUtils;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -39,8 +40,8 @@ public class StudyJavaFxToolWindow extends StudyToolWindow {
}
@Override
public void setText(String text) {
public void setText(@NotNull String text) {
StudyPluginConfigurator configurator = StudyUtils.getConfigurator(ProjectUtil.guessCurrentProject(this));
myBrowserWindow.loadContent(text, configurator);
myBrowserWindow.loadContent(text, configurator);
}
}
@@ -3,8 +3,13 @@ package com.jetbrains.edu.learning.ui;
import com.intellij.facet.ui.FacetValidatorsManager;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DefaultProjectFactory;
import com.intellij.openapi.project.DefaultProjectFactoryImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.DialogWrapper;
@@ -14,18 +19,20 @@ import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.AncestorListenerAdapter;
import com.intellij.util.Consumer;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.stepic.EduStepicConnector;
import com.jetbrains.edu.learning.stepic.StudySettings;
import com.jetbrains.edu.learning.stepic.StepicUser;
import icons.InteractiveLearningIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -36,10 +43,11 @@ import java.util.List;
* author: liana
* data: 7/31/14.
*/
public class StudyNewProjectPanel{
public class StudyNewProjectPanel {
private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class);
private List<CourseInfo> myAvailableCourses = new ArrayList<CourseInfo>();
private JButton myBrowseButton;
private JComboBox myCoursesComboBox;
private JComboBox<CourseInfo> myCoursesComboBox;
private JButton myRefreshButton;
private JPanel myContentPanel;
private JLabel myAuthorLabel;
@@ -50,19 +58,37 @@ public class StudyNewProjectPanel{
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;
private boolean isComboboxInitialized;
public StudyNewProjectPanel(StudyProjectGenerator generator) {
public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
myGenerator = generator;
myAvailableCourses = myGenerator.getCourses(false);
myBrowseButton.setPreferredSize(new Dimension(28, 28));
myRefreshButton.setPreferredSize(new Dimension(28, 28));
if (myAvailableCourses.isEmpty()) {
initListeners();
myRefreshButton.setVisible(true);
myRefreshButton.putClientProperty("JButton.buttonType", "null");
myRefreshButton.setIcon(AllIcons.Actions.Refresh);
myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
myContentPanel.addAncestorListener(new AncestorListenerAdapter() {
@Override
public void ancestorMoved(AncestorEvent event) {
if (!isComboboxInitialized && myContentPanel.isVisible()) {
isComboboxInitialized = true;
initCoursesCombobox();
}
}
});
}
private void initCoursesCombobox() {
myAvailableCourses =
myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
if (myAvailableCourses == null || myAvailableCourses.isEmpty()) {
setError(CONNECTION_ERROR);
}
else {
for (CourseInfo courseInfo : myAvailableCourses) {
myCoursesComboBox.addItem(courseInfo);
}
addCoursesToCombobox(myAvailableCourses);
final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
@@ -72,13 +98,6 @@ public class StudyNewProjectPanel{
myGenerator.setSelectedCourse(selectedCourse);
setOK();
}
initListeners();
myRefreshButton.setVisible(true);
myRefreshButton.putClientProperty("JButton.buttonType", "null");
myRefreshButton.setIcon(AllIcons.Actions.Refresh);
myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
}
private void setupBrowseButton() {
@@ -177,21 +196,22 @@ public class StudyNewProjectPanel{
private class RefreshActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
refreshCoursesList();
final List<CourseInfo> courses =
myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
if (courses != null) {
refreshCoursesList(courses);
}
}
}
private void refreshCoursesList() {
final List<CourseInfo> courses = myGenerator.getCourses(true);
private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
if (courses.isEmpty()) {
setError(CONNECTION_ERROR);
return;
}
myCoursesComboBox.removeAllItems();
for (CourseInfo courseInfo : courses) {
myCoursesComboBox.addItem(courseInfo);
}
addCoursesToCombobox(courses);
myGenerator.setSelectedCourse(StudyUtils.getFirst(courses));
myGenerator.setCourses(courses);
@@ -199,6 +219,20 @@ public class StudyNewProjectPanel{
StudyProjectGenerator.flushCache(myAvailableCourses);
}
private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
for (CourseInfo courseInfo : courses) {
if (!courseInfo.isAdaptive()) {
myCoursesComboBox.addItem(courseInfo);
}
else {
final boolean isLoggedIn = myGenerator.myUser != null;
if (isLoggedIn) {
myCoursesComboBox.addItem(courseInfo);
}
}
}
}
/**
* Handles selecting course in combo box
@@ -213,10 +247,11 @@ public class StudyNewProjectPanel{
if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
myAuthorLabel.setText("");
myDescriptionLabel.setText("");
setError(INVALID_COURSE);
return;
}
final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ?"Author: " + authorsString : "");
myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
myDescriptionLabel.setText(selectedCourse.getDescription());
myGenerator.setSelectedCourse(selectedCourse);
@@ -238,6 +273,7 @@ public class StudyNewProjectPanel{
protected AddRemoteDialog() {
super(null);
setTitle("Login To Stepic");
myRemoteCourse = new StudyAddRemoteCourse();
init();
}
@@ -248,8 +284,15 @@ public class StudyNewProjectPanel{
return myRemoteCourse.getContentPanel();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myRemoteCourse.getLoginField();
}
@Override
protected void doOKAction() {
super.doOKAction();
if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
myRemoteCourse.setError("Please, enter your login");
return;
@@ -259,15 +302,26 @@ public class StudyNewProjectPanel{
return;
}
super.doOKAction();
final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
if (!isSuccess) {
setError("Failed to log in");
}
StudySettings.getInstance().setLogin(myRemoteCourse.getLogin());
StudySettings.getInstance().setPassword(myRemoteCourse.getPassword());
refreshCoursesList();
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(),
myRemoteCourse.getPassword()));
if (stepicUser != null) {
stepicUser.setEmail(myRemoteCourse.getLogin());
stepicUser.setPassword(myRemoteCourse.getPassword());
myGenerator.myUser = stepicUser;
final List<CourseInfo> courses = myGenerator.getCoursesAsynchronouslyIfNeeded(true);
if (courses != null) {
ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
}
}
else {
setError("Failed to login");
}
}, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
}
}
}
@@ -22,6 +22,7 @@ import com.intellij.ui.BrowserHyperlinkListener;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@@ -61,7 +62,7 @@ public class StudySwingToolWindow extends StudyToolWindow {
return scrollPane;
}
public void setText(String text) {
public void setText(@NotNull String text) {
myTaskTextPane.setText(text);
}
}
@@ -0,0 +1,55 @@
package com.jetbrains.edu.learning.ui
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.ToolWindow
import com.jetbrains.edu.learning.StudyUtils
import com.jetbrains.python.console.PythonConsoleView
class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val currentTask = StudyUtils.getCurrentTask(project)
if (currentTask != null) {
val sdk = StudyUtils.findSdk(currentTask, project)
if (sdk != null) {
val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk);
testResultsToolWindow.isEditable = false
testResultsToolWindow.isConsoleEditorEnabled = false
testResultsToolWindow.prompt = null
toolWindow.isToHideOnEmptyContent = true
val contentManager = toolWindow.contentManager
val content = contentManager.factory.createContent(testResultsToolWindow.component, null, false)
contentManager.addContent(content)
project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow))
}
else {
StudyUtils.showNoSdkNotification(currentTask, project)
}
}
}
fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener {
return object : FileEditorManagerListener {
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
}
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
toolWindow.setAvailable(false, {})
}
override fun selectionChanged(event: FileEditorManagerEvent) {
toolWindow.setAvailable(false, {})
}
}
}
}
@@ -35,6 +35,7 @@ import com.intellij.util.ui.JBUI;
import com.jetbrains.edu.learning.*;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -45,6 +46,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
private static final String TASK_INFO_ID = "taskInfo";
private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
private final JBCardLayout myCardLayout;
private final JPanel myContentPanel;
private final OnePixelSplitter mySplitPane;
@@ -63,7 +65,14 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
JPanel toolbarPanel = createToolbarPanel(getActionGroup(project));
setToolbar(toolbarPanel);
myContentPanel.add(TASK_INFO_ID, createTaskInfoPanel(project));
final JPanel panel = new JPanel(new BorderLayout());
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null && course.isAdaptive()) {
panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH);
}
JComponent taskInfoPanel = createTaskInfoPanel(project);
panel.add(taskInfoPanel, BorderLayout.CENTER);
myContentPanel.add(TASK_INFO_ID, panel);
mySplitPane.setFirstComponent(myContentPanel);
addAdditionalPanels(project);
myCardLayout.show(myContentPanel, TASK_INFO_ID);
@@ -76,14 +85,15 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
}
if (StudyTaskManager.getInstance(project).isTurnEditingMode() || StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
if (StudyTaskManager.getInstance(project).isTurnEditingMode() ||
StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
TaskFile file = StudyUtils.getSelectedTaskFile(project);
if (file != null) {
VirtualFile taskDir = file.getTask().getTaskDir(project);
setTaskText(taskText, taskDir, project);
}
} else {
}
else {
setTaskText(taskText, null, project);
}
}
@@ -98,6 +108,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
}
}
public void dispose() {
}
@@ -180,7 +191,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
}
}
protected abstract void setText(String text);
protected abstract void setText(@NotNull String text);
public void setEmptyText(@NotNull Project project) {
if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
@@ -7,6 +7,7 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.jetbrains.edu.learning.StudyProjectComponent;
import com.jetbrains.edu.learning.StudyTaskManager;
import com.jetbrains.edu.learning.StudyUtils;
import com.jetbrains.edu.learning.courseFormat.Course;
@@ -23,7 +24,6 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
final Course course = taskManager.getCourse();
if (course != null) {
final StudyToolWindow studyToolWindow;
if (StudyUtils.hasJavaFx() && StudyTaskManager.getInstance(project).shouldUseJavaFx()) {
studyToolWindow = new StudyJavaFxToolWindow();
@@ -21,6 +21,8 @@
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module" module-name="platform-api" />
<orderEntry type="module" module-name="python-community" />
<orderEntry type="module" module-name="xml" />
<orderEntry type="library" name="markdownj" level="project" />
</component>
@@ -9,7 +9,7 @@ def get_file_text(path):
return text
def get_file_output(encoding="utf-8", path=sys.argv[-1]):
def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""):
"""
Returns answer file output
:param encoding: to decode output in python3
@@ -18,7 +18,13 @@ def get_file_output(encoding="utf-8", path=sys.argv[-1]):
"""
import subprocess
proc = subprocess.Popen([sys.executable, path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
proc = subprocess.Popen([sys.executable, path], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
if arg_string:
for arg in arg_string.split("\n"):
proc.stdin.write(bytearray(str(arg) + "\n", encoding))
proc.stdin.flush()
return list(map(lambda x: str(x.decode(encoding)), proc.communicate()[0].splitlines()))
@@ -31,7 +37,8 @@ def test_file_importable():
parent = os.path.abspath(os.path.join(path, os.pardir))
python_files = [f for f in os.listdir(parent) if os.path.isfile(os.path.join(parent, f)) and f.endswith(".py")]
for python_file in python_files:
if python_file == "tests.py": continue
if python_file == "tests.py":
continue
check_importable_path(os.path.join(parent, python_file))
return
check_importable_path(path)
@@ -187,6 +194,28 @@ def get_answer_placeholders():
return windows
def check_samples(samples=()):
"""
Check script output for all samples. Sample is a two element list, where the first is input and
the second is output.
"""
for sample in samples:
if len(sample) == 2:
output = get_file_output(arg_string=str(sample[0]))
if "\n".join(output) != sample[1]:
failed(
"Test from samples failed: \n \n"
"Input:\n{}"
"\n \n"
"Expected:\n{}"
"\n \n"
"Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output)))
return
set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.")
passed()
def run_common_tests(error_text="Please, reload file and try again"):
test_is_initial_text()
test_is_not_empty()
@@ -15,12 +15,13 @@ import com.jetbrains.edu.learning.actions.StudyRunAction;
import com.jetbrains.edu.learning.checker.StudyCheckTask;
import com.jetbrains.edu.learning.checker.StudyCheckUtils;
import com.jetbrains.edu.learning.checker.StudyTestRunner;
import com.jetbrains.edu.learning.checker.StudyTestsOutputParser;
import com.jetbrains.edu.learning.core.EduNames;
import com.jetbrains.edu.learning.courseFormat.Course;
import com.jetbrains.edu.learning.courseFormat.StudyStatus;
import com.jetbrains.edu.learning.courseFormat.Task;
import com.jetbrains.edu.learning.courseFormat.TaskFile;
import com.jetbrains.edu.learning.editor.StudyEditor;
import com.jetbrains.edu.learning.ui.StudyToolWindow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,8 +42,8 @@ public class PyStudyCheckAction extends StudyCheckAction {
}
if (StudyCheckUtils.hasBackgroundProcesses(project)) return;
if (!runTask(project)) return;
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null && !course.isAdaptive() && !runTask(project)) return;
final Task task = studyState.getTask();
final VirtualFile taskDir = studyState.getTaskDir();
@@ -90,34 +91,48 @@ public class PyStudyCheckAction extends StudyCheckAction {
final Process testProcess,
final String commandLine) {
return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) {
@Override
protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
ApplicationManager.getApplication().invokeLater(() -> {
if (myTaskDir == null) return;
myTaskManger.setStatus(myTask, StudyStatus.Failed);
for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
final String name = entry.getKey();
final TaskFile taskFile = entry.getValue();
if (taskFile.getAnswerPlaceholders().size() < 2) {
myTaskManger.setStatus(taskFile, StudyStatus.Failed);
continue;
}
if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project)));
}
}
StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project);
StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
});
@Override
protected void onTaskFailed(String message) {
ApplicationManager.getApplication().invokeLater(() -> {
if (myTaskDir == null) return;
myTaskManger.setStatus(myTask, StudyStatus.Failed);
for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
final String name = entry.getKey();
final TaskFile taskFile = entry.getValue();
if (taskFile.getAnswerPlaceholders().size() < 2) {
myTaskManger.setStatus(taskFile, StudyStatus.Failed);
continue;
}
};
if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project);
}));
}
}
final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
if (toolWindow != null) {
final Course course = StudyTaskManager.getInstance(project).getCourse();
if (course != null) {
if (course.isAdaptive()) {
StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), project);
StudyCheckUtils.showTestResultsToolWindow(project, message, false);
}
else {
StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project);
}
}
StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
}
});
}
};
}
@Nullable
private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState,
@NotNull final Task task,
@NotNull final VirtualFile taskDir) {
@NotNull final Task task,
@NotNull final VirtualFile taskDir) {
VirtualFile taskVirtualFile = studyState.getVirtualFile();
for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
String name = entry.getKey();
@@ -131,7 +146,7 @@ public class PyStudyCheckAction extends StudyCheckAction {
}
return taskVirtualFile;
}
@NotNull
@Override
public String getActionId() {
@@ -6,17 +6,19 @@ import com.intellij.facet.ui.ValidationResult;
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.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import com.jetbrains.edu.learning.stepic.CourseInfo;
import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
import com.jetbrains.python.newProject.PythonProjectGenerator;
import icons.InteractiveLearningPythonIcons;
import org.jetbrains.annotations.Nls;
@@ -89,14 +91,14 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp
throw new UnsupportedOperationException();
}
public void validate() {
fireStateChanged();
ApplicationManager.getApplication().invokeLater(()->fireStateChanged());
}
});
return settingsPanel.getContentPanel();
}
public List<CourseInfo> getCourses() {
return myGenerator.getCourses(false);
return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
}
public void setSelectedCourse(CourseInfo course) {