Resolve conflicts
authorValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Wed, 18 May 2016 09:51:46 +0000 (12:51 +0300)
committerValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Wed, 18 May 2016 09:51:46 +0000 (12:51 +0300)
32 files changed:
python/educational-core/student/resources/META-INF/plugin.xml
python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java
python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditor.java
python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyAddRemoteCourse.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java
python/educational-core/student/student.iml
python/educational-python/resources/fileTemplates/internal/test_helper.py.ft
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java

index 996451b7eec72273d86bcd9504d45af689810b5a..b020355f8c1b40867915646243a62ed51ca94c7f 100644 (file)
     <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"/>
index 4fde0a889748b22bb30b9322bfe80f859445f2b1..9fbfb736a5e494bed98140782dbaef49d3431ab2 100644 (file)
@@ -66,6 +66,7 @@ public abstract class StudyBasePluginConfigurator implements StudyPluginConfigur
           Task task = getTask(file);
           setTaskText(task, file.getParent());
         }
+        toolWindow.setBottomComponent(null);
       }
 
       @Nullable
index 024cd09e924590fa9666f1acfc1a660911fca220..82942814d2f8dc452a6ac962cd227a53162ab40c 100644 (file)
@@ -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);
@@ -279,4 +282,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;
+  }
 }
index 2c41cda8d574f76a3a7b74573874c4fe3a55be37..63f6d29180f4eb9e6d6c9b9d7324788d992c1455 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.fileEditor.FileEditor;
 import com.intellij.openapi.fileEditor.FileEditorManager;
 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
 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;
@@ -43,6 +44,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;
@@ -359,7 +361,7 @@ public class StudyUtils {
     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;
     }
@@ -481,7 +483,7 @@ public class StudyUtils {
     return null;
   }
 
-
+  @Nullable
   public static String getTaskText(@NotNull final Project project) {
     TaskFile taskFile = getSelectedTaskFile(project);
     if (taskFile == null) {
@@ -505,12 +507,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");
+      }
     }
   }
 
@@ -518,6 +541,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");
index f8302304b9b09f4524dcde9ed73d71d1d208fee1..fc6d39649c6b60c025133cdbf20775e0f6448721 100644 (file)
@@ -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,12 +73,37 @@ 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;
     }
 
 
@@ -88,39 +116,61 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
                                                                                             myProject));
       //log error output of tests
       LOG.info("#educational " + stderr);
-      return;
+      return null;
     }
+    return testsOutput;
+  }
 
-    postAttemptToStepic(testsOutput);
-
-
-    if (testsOutput.isSuccess()) {
-      onTaskSolved(testsOutput);
-    }
-    else {
-      onTaskFailed(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());
+      }
     }
-    runAfterTaskSolvedActions();
   }
 
-  protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
+  protected void onTaskFailed(String message) {
     myTaskManger.setStatus(myTask, StudyStatus.Failed);
     ApplicationManager.getApplication().invokeLater(
-      () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), myProject));
+      () -> StudyCheckUtils.showTestResults(myProject, message, false));
   }
 
-  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));
+      () -> StudyCheckUtils.showTestResults(myProject, message, true));
   }
 
-  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 +180,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);
index 33c626a05bcb5e191afc515167ef0d59d9378412..38f6e8f91162ca64232618f3ce0a952737ba000b 100644 (file)
@@ -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 {
 
@@ -193,4 +195,31 @@ public class StudyCheckUtils {
       EduUtils.flushWindows(taskFile, virtualFile, true);
     }
   }
+
+  public static void showTestResults(@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;
+      }
+    }
+  }
 }
index 55bfe7273bac72814873e355c6a1b0bf89d83efe..fc21b40bc5791d52aabde47dbcc3fcc600b752d5 100644 (file)
@@ -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 ";
@@ -31,8 +33,10 @@ public class StudyTestsOutputParser {
   @NotNull
   public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) {
     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,18 @@ public class StudyTestsOutputParser {
         }
 
         if (line.contains(TEST_FAILED)) {
-          return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
+          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());
         }
       }
     }
index e643d9470cc9915c94db4d743164d10364420b7f..6132952a8274868c1110bde29f180a0275a5ca68 100644 (file)
@@ -7,7 +7,7 @@ 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;
@@ -20,7 +20,7 @@ public class Course {
   @Expose private String description;
   @Expose private String name;
   private String myCourseDirectory = "";
-  @Expose private List<CourseInfo.Author> authors = new ArrayList<CourseInfo.Author>();
+  @Expose private List<StepicUser> authors = new ArrayList<StepicUser>();
   private boolean myUpToDate;
 
   @Expose @SerializedName("language")
@@ -32,6 +32,11 @@ public class Course {
 
   //this field is used to distinguish study and course creator modes
   private String courseMode = EduNames.STUDY;
+  
+  @Expose private boolean isAdaptive;
+  
+  @Expose
+  private int id;
 
   /**
    * Initializes state of course
@@ -72,25 +77,25 @@ public class Course {
   }
 
   @NotNull
-  public List<CourseInfo.Author> getAuthors() {
+  public List<StepicUser> getAuthors() {
     return authors;
   }
 
-  public static String getAuthorsString(@NotNull List<CourseInfo.Author> authors) {
-    return StringUtil.join(authors, new Function<CourseInfo.Author, String>() {
+  public static String getAuthorsString(@NotNull List<StepicUser> authors) {
+    return StringUtil.join(authors, new Function<StepicUser, String>() {
       @Override
-      public String fun(CourseInfo.Author author) {
+      public String fun(StepicUser author) {
         return 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) : ""));
     }
   }
 
@@ -138,7 +143,7 @@ public class Course {
     myLanguage = language;
   }
 
-  public void setAuthors(List<CourseInfo.Author> authors) {
+  public void setAuthors(List<StepicUser> authors) {
     this.authors = authors;
   }
 
@@ -151,6 +156,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;
   }
index 9af4dc7af1a56f141d4c623bcbec015f7486dad2..fe5cd4e65c40b909b67504145b8ba6ae9139667a 100644 (file)
@@ -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;
@@ -23,6 +23,7 @@ public class StudyGenerator {
   private StudyGenerator() {
 
   }
+
   private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName());
 
   /**
@@ -107,34 +108,32 @@ 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(new FilenameFilter() {
-                  @Override
-                  public boolean accept(File dir, String name) {
-                    return !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);
+    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(new FilenameFilter() {
+        @Override
+        public boolean accept(File dir, String name) {
+          return !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;
+        }
 
-                }
-              }
-              catch (IOException e) {
-                LOG.error(e);
-              }
+        FileUtil.copy(file, dir);
+      }
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
   }
-
 }
index 908a0e2159481f99c4383bbc8d29d383284c58f0..fa5d05fdc5589dc82497b2361c080bf4b18f5ee5 100644 (file)
@@ -22,17 +22,18 @@ 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;
@@ -44,16 +45,18 @@ import java.util.List;
 import java.util.Map;
 
 public class StudyProjectGenerator {
+  public static final String AUTHOR_ATTRIBUTE = "authors";
+  public static final String LANGUAGE_ATTRIBUTE = "language";
+  public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__";
+  public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses");
   private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName());
-  private final List<SettingsListener> myListeners = ContainerUtil.newArrayList();
-  protected static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses");
+  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;
-  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 void setCourses(List<CourseInfo> courses) {
     myCourses = courses;
@@ -64,48 +67,68 @@ 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);
+  @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()) {
-        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;
+        return readCourseFromCache(courseFile, false);
       }
+      else {
+        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);
+        }
+        
+      }
+    final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
+    if (course != null) {
+      flushCourse(project, course);
+      course.initCourse(false);
     }
-    catch (FileNotFoundException | UnsupportedEncodingException e) {
-      LOG.error(e);
+    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) {
@@ -142,8 +165,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);
 
@@ -217,6 +240,9 @@ 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());
@@ -235,7 +261,7 @@ public class StudyProjectGenerator {
     }
   }
 
-  private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
+  public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
     final Gson gson = new GsonBuilder().setPrettyPrinting().create();
     final String json = gson.toJson(course);
     final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
@@ -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;
@@ -308,7 +334,7 @@ public class StudyProjectGenerator {
   }
 
   public List<CourseInfo> getCourses(boolean force) {
-    if (ourCoursesDir.exists()) {
+    if (OUR_COURSES_DIR.exists()) {
       myCourses = getCoursesFromCache();
     }
     if (force || myCourses.isEmpty()) {
@@ -336,7 +362,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 +373,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 +386,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) {
@@ -389,12 +417,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;
@@ -466,10 +496,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);
       }
index 50f8ad8cf759f4013872052b2885ed2cca84deaf..2985e125d38ac05018c768a778302d82f49a2aee 100644 (file)
@@ -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) {
index cdfd8d4535db5e0cc09a45c70e8d67b06148911b..34ee809bb0cff6cf3be0f83eb6369fa81ae18edf 100644 (file)
@@ -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) {
index 2bc2099924d9687703b95f6ba092c6b9da31e3b9..eff3c6f3684eb516f3916ef0375173fa71829e36 100644 (file)
@@ -12,20 +12,23 @@ 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";
 
-  List<Integer> instructors = new ArrayList<Integer>();
-
-  List<Author> myAuthors = new ArrayList<Author>();
+  private boolean isAdaptive;
+  
   int id;
+  boolean is_public;
+  public List<Integer> sections;
+  List<Integer> instructors = new ArrayList<Integer>();
+  List<StepicUser> myAuthors = new ArrayList<>();
 
   public static CourseInfo INVALID_COURSE = new CourseInfo();
 
@@ -34,7 +37,7 @@ public class CourseInfo {
   }
 
   @NotNull
-  public List<Author> getAuthors() {
+  public List<StepicUser> getAuthors() {
     return myAuthors;
   }
 
@@ -93,18 +96,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 +119,12 @@ public class CourseInfo {
   public void setType(String type) {
     myType = type;
   }
+
+  public boolean isAdaptive() {
+    return isAdaptive;
+  }
+
+  public void setAdaptive(boolean adaptive) {
+    isAdaptive = adaptive;
+  }
 }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java
new file mode 100644 (file)
index 0000000..7a6caed
--- /dev/null
@@ -0,0 +1,524 @@
+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.actions.StudyNextStudyTaskAction;
+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());
+          }
+          VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+          ProjectView.getInstance(project).refresh();
+        }
+      }
+      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);
+            flushAdaptiveCourse(course, adaptive, unsolvedTask);
+            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());
+            }
+          }
+          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);
+            flushAdaptiveCourse(course, adaptive, unsolvedTask);
+            adaptive.initLesson(course, true);
+            ApplicationManager.getApplication().invokeLater(()->new StudyNextStudyTaskAction().navigateTask(project));
+          }
+        }
+        ApplicationManager.getApplication().invokeLater(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false));
+      }
+      else {
+        LOG.warn("Recommendation reactions weren't posted");
+      }
+    }
+  }
+
+  private static void flushAdaptiveCourse(Course course, Lesson adaptive, Task unsolvedTask) {
+    unsolvedTask.setLesson(null);
+    final TaskFile file = unsolvedTask.getTaskFile(DEFAULT_TASKFILE_NAME);
+    if (file != null) {
+      file.setTask(null);
+    }
+    adaptive.setCourse(null);
+    StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
+    course.initCourse(true);
+  }
+
+  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 numer: " + 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);
+  }
+}
index 8d871be6a2e2a5d9d1814a9c4e06a71322027374..c7169e83269a4cfe244a825d98e63d911b3626cd 100644 (file)
@@ -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,12 +52,13 @@ 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;
 
+
   //this prefix indicates that course can be opened by educational plugin
   public static final String PYCHARM_PREFIX = "pycharm";
   private static BasicCookieStore ourCookieStore;
@@ -70,15 +66,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 +92,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,7 +111,7 @@ public class EduStepicConnector {
     return true;
   }
 
-  private static void initializeClient() {
+  public 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"));
@@ -164,14 +166,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 +177,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 +191,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 +208,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() {
@@ -231,16 +236,16 @@ public class EduStepicConnector {
 
   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 +255,56 @@ 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 +318,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 +337,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;
   }
 
 
@@ -337,14 +364,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");
+    final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "ATTEMPTS_URL");
     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 {
@@ -355,12 +381,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);
     }
@@ -369,11 +395,11 @@ public class EduStepicConnector {
     }
   }
 
-  private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList<SolutionFile> files) throws IOException {
-    final HttpPost request = new HttpPost(stepicApiUrl + "submissions");
+  private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
+    final HttpPost request = new HttpPost(stepicApiUrl + "SUBMISSION_URL");
     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();
@@ -401,15 +427,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 {
@@ -419,13 +445,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;
@@ -447,14 +473,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();
       }
     }
@@ -506,8 +531,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;
@@ -532,11 +557,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));
@@ -549,7 +574,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) {
@@ -558,17 +584,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 {
@@ -597,14 +623,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 {
@@ -659,7 +685,7 @@ public class EduStepicConnector {
     ApplicationManager.getApplication().invokeLater(new Runnable() {
       @Override
       public void run() {
-        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 {
@@ -678,280 +704,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(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()));
-        }
-      }
-    }
-  }
-
-  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);
-    }
-  }
-
 }
index bb95494d1540d6c92a5d5c7e579abd3d3e662ca8..09a132765873de2df00f88ab947caa5b0f7c0e25 100644 (file)
@@ -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");
     }
   }
 
index c3994496fef7e53570cfe926afcfa0db3f825719..097c4a64ef9c4aca23f9976388209a5cae5b1199 100644 (file)
  */
 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();
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java
new file mode 100644 (file)
index 0000000..ff40e34
--- /dev/null
@@ -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}, " ");
+  }
+}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java
new file mode 100644 (file)
index 0000000..c8e970f
--- /dev/null
@@ -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;
+    }
+  }
+}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java
deleted file mode 100644 (file)
index 8941d43..0000000
+++ /dev/null
@@ -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;
-  }
-}
\ No newline at end of file
index adce70291557e8acd0de760fc661bd2c1555139a..f61e12f93599a3857d83f18327b3dd240beda61a 100644 (file)
@@ -40,6 +40,10 @@ public class StudyAddRemoteCourse {
     return myContentPanel;
   }
 
+  public JTextField getLoginField() {
+    return myLoginField;
+  }
+
   public String getPassword() {
     return String.valueOf(myPasswordField.getPassword());
   }
index fd7a12a5e01e8efc20228845c643ef87d31c9145..75d6146c48c984eeb873ea21d92021b4a79f399a 100644 (file)
@@ -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;
index 058bd633a29b342aca10117dda6e2a7f68c68319..d9507f11164f29316c52fae67eb4ed7f6c0cea5b 100644 (file)
@@ -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);
   }
 }
index 89fa41319b72d76455240b62a99cb418af27b6e6..89ef2ae07938614451ea8bb3f3e2c2d4dc29c971 100644 (file)
@@ -3,8 +3,12 @@ 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.DefaultProjectFactoryImpl;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.ui.DialogWrapper;
@@ -15,12 +19,12 @@ import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.Consumer;
-import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.StudyUtils;
 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;
@@ -31,15 +35,20 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 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;
@@ -78,7 +87,6 @@ public class StudyNewProjectPanel{
     myRefreshButton.setIcon(AllIcons.Actions.Refresh);
 
     myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
-
   }
 
   private void setupBrowseButton() {
@@ -221,7 +229,7 @@ public class StudyNewProjectPanel{
         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);
@@ -253,6 +261,12 @@ public class StudyNewProjectPanel{
       return myRemoteCourse.getContentPanel();
     }
 
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myRemoteCourse.getLoginField();
+    }
+
     @Override
     protected void doOKAction() {
       if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
@@ -265,14 +279,45 @@ public class StudyNewProjectPanel{
       }
 
       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();
+      final ProgressManager progressManager = ProgressManager.getInstance();
+      progressManager.runProcessWithProgressSynchronously(() -> {
+        final Future<StepicUser> future = ApplicationManager.getApplication().executeOnPooledThread(
+          new Callable<StepicUser>() {
+            @Override
+            public StepicUser call() throws Exception {
+              return EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
+            }
+          });
+
+        while (!future.isCancelled() && !future.isDone()) {
+          progressManager.getProgressIndicator().checkCanceled();
+          try {
+            TimeUnit.MILLISECONDS.sleep(500);
+          }
+          catch (final InterruptedException e) {
+            LOG.warn(e.getMessage());
+          }
+        }
+
+        try {
+          final StepicUser stepicUser = future.get();
+          if (stepicUser != null) {
+            stepicUser.setEmail(myRemoteCourse.getLogin());
+            stepicUser.setPassword(myRemoteCourse.getPassword());
+            myGenerator.myUser = stepicUser;
+            ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList);
+          }
+          else {
+            setError("Failed to log in");
+          }
+        }
+        catch (InterruptedException e) {
+          LOG.warn(e.getMessage());
+        }
+        catch (ExecutionException e) {
+          LOG.warn(e.getMessage());
+        }
+      }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
     }
   }
-
 }
index ebaff662ecb84db332586cf2249bc620478cdc3f..be13a1b0f46d08ce753012685bd627e2cd7c749c 100644 (file)
@@ -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);
   }
 }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt
new file mode 100644 (file)
index 0000000..0035ab8
--- /dev/null
@@ -0,0 +1,53 @@
+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);
+
+        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, {})
+      }
+    }
+  }
+}
+
+
index feb06b72159a898193fa3b6b1f490c96e8eb4027..589d0179f6ad289ddf3f273839f4c6c1e1cc6a93 100644 (file)
@@ -25,21 +25,30 @@ import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 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.project.ProjectUtil;
 import com.intellij.openapi.ui.SimpleToolWindowPanel;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.JBCardLayout;
+import com.intellij.ui.JBColor;
 import com.intellij.ui.OnePixelSplitter;
 import com.intellij.util.ui.JBUI;
 import com.jetbrains.edu.learning.*;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
 import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 import java.util.Map;
 
 public abstract class StudyToolWindow extends SimpleToolWindowPanel implements DataProvider, Disposable {
@@ -63,8 +72,15 @@ 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(createReactionPanel(), BorderLayout.NORTH);
+    }
+    JComponent taskInfoPanel = createTaskInfoPanel(taskText, project);
+    panel.add(taskInfoPanel, BorderLayout.CENTER);
+    myContentPanel.add(TASK_INFO_ID, panel);
     mySplitPane.setFirstComponent(myContentPanel);
     addAdditionalPanels(project);
     myCardLayout.show(myContentPanel, TASK_INFO_ID);
@@ -98,7 +114,67 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
       }
     }
   }
+  
+  public JPanel createReactionPanel() {
+    final JPanel panel = new JPanel(new GridBagLayout());
+    panel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, JBColor.border()));
+    final JPanel hardPanel = new JPanel(new BorderLayout());
+    hardPanel.add(new JLabel("Too hard"), BorderLayout.CENTER);
+    hardPanel.setBorder(BorderFactory.createEtchedBorder());
+
+    final JPanel boringPanel = new JPanel(new BorderLayout());
+    boringPanel.setBorder(BorderFactory.createEtchedBorder());
+    boringPanel.add(new JLabel("Too boring"), BorderLayout.CENTER);
+
+    final GridBagConstraints c = new GridBagConstraints();
+    c.fill  = GridBagConstraints.HORIZONTAL;
+    c.gridx = 0;
+    c.gridy = 0;
+    c.weightx = 1;
+    panel.add(hardPanel, c);
+    c.gridx =  1;
+    panel.add(boringPanel, c);
+    c.gridy = 1;
+    c.weightx = 1;
+    panel.add(Box.createVerticalBox(), c);
+
+    final Project project = ProjectUtil.guessCurrentProject(myContentPanel);
+    addMouseListener(hardPanel, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, 0));
+    addMouseListener(boringPanel, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, -1));
+    
+    return panel;
+  }
+
+  private static void addMouseListener(@NotNull final JPanel panel, @NotNull Runnable onClickAction ) {
+    panel.addMouseListener(new MouseAdapter() {
+      @Override
+      public void mouseClicked(MouseEvent e) {
+        if (e.getClickCount() == 1) {
+          final ProgressIndicatorBase progress = new ProgressIndicatorBase();
+          progress.setText("Loading Next Recommendation");
+          ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(panel),
+                                                                    "Loading Next Recommendation") {
+
+            @Override
+            public void run(@NotNull ProgressIndicator indicator) {
+              onClickAction.run();
+            }
+          });
+        }
+      }
 
+      @Override
+      public void mouseEntered(MouseEvent e) {
+        panel.setBackground(UIUtil.getTreeSelectionBackground());
+      }
+
+      @Override
+      public void mouseExited(MouseEvent e) {
+        panel.setBackground(UIUtil.getLabelBackground());
+      }
+    });
+  }
+  
   public void dispose() {
   }
 
@@ -181,7 +257,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) {
index 886936b22112b30acf01b793c6b66868bd2c3aad..4530e004504dc042433ff1c05e2c1ec6ccb71f4f 100644 (file)
@@ -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();
index 80c21a26244ec50c3d06319bc5cf8fe01ae641fe..0758b722ce7cd9d1e561d9e3bb1988276414bea8 100644 (file)
@@ -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" />
   </component>
 </module>
\ No newline at end of file
index 687bdd580a95062e980762d0f5ceae79357df4df..250f253d8decbd6f57be9180d7a296b08df184f5 100644 (file)
@@ -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,30 @@ 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(sample[0], 
+                                                        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()
index fdb110718b3401f5a580a6754481228f6e825a88..0f5ab396df3f5f5e01c5bc20ae3ca12ed7707afd 100644 (file)
@@ -7,7 +7,6 @@ import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.wm.IdeFocusManager;
 import com.jetbrains.edu.learning.actions.StudyCheckAction;
@@ -17,10 +16,12 @@ 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;
 
@@ -42,8 +43,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();
@@ -92,36 +93,39 @@ 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) {
+            StudyCheckUtils.showTestResults(project, message, false);
+            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();
@@ -135,7 +139,7 @@ public class PyStudyCheckAction extends StudyCheckAction {
     }
     return taskVirtualFile;
   }
-  
+
   @NotNull
   @Override
   public String getActionId() {
index df9db88d18aeb654bd9970d725c761eea87b1214..551b831f1d5bb76e14089b68ffa1d7a4012d062f 100644 (file)
@@ -6,6 +6,7 @@ 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;
@@ -15,8 +16,8 @@ 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,7 +90,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp
         throw new UnsupportedOperationException();
       }
       public void validate() {
-        fireStateChanged();
+        ApplicationManager.getApplication().invokeLater(()->fireStateChanged());
       }
     });
     return settingsPanel.getContentPanel();