Merge remote-tracking branch 'origin/master' phpstorm/163.214
authorValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Wed, 15 Jun 2016 13:12:21 +0000 (16:12 +0300)
committerValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Wed, 15 Jun 2016 13:12:21 +0000 (16:12 +0300)
43 files changed:
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java
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/actions/StudyRefreshAnswerPlaceholder.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.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/StudySmartChecker.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/courseFormat/Lesson.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.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/StepicAdaptiveReactionsPanel.java [new file with mode: 0644]
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 d828102bfb2212da74a5f9eadb47af4273b31214..75634c0a9026fd21468fae82742f580d9ffc0082 100644 (file)
@@ -17,6 +17,7 @@ package com.jetbrains.edu.coursecreator;
 
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiElement;
@@ -68,7 +69,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
 
     private static void tryToRenameTaskFile(PsiFile file, String oldName) {
       final PsiDirectory taskDir = file.getContainingDirectory();
-      Course course = StudyTaskManager.getInstance(file.getProject()).getCourse();
+      final Project project = file.getProject();
+      Course course = StudyTaskManager.getInstance(project).getCourse();
       if (course == null) {
         return;
       }
@@ -93,7 +95,7 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL
         return;
       }
       ApplicationManager.getApplication().runWriteAction(() -> {
-        VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, oldName);
+        VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, oldName);
         if (patternFile != null) {
           try {
             patternFile.delete(CCRefactoringElementListenerProvider.class);
index 6ac5ab418049707eb16ea5120ca1d5d11dfba591..e1504ce29eded44793f14f3935cc109001691c22 100644 (file)
@@ -221,7 +221,7 @@ public class CCUtils {
       if (child == null) {
         continue;
       }
-      Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
+      Document patternDocument = StudyUtils.getPatternDocument(project, entry.getValue(), name);
       Document document = FileDocumentManager.getInstance().getDocument(child);
       if (document == null || patternDocument == null) {
         return;
index 5eae370909ee4d5cbce19b63cc5c9601eb835032..254d64dd534cd1499af9b47e8f3674d020b02d67 100644 (file)
@@ -85,7 +85,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
   }
 
   private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) {
-    Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName());
+    Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, file.getName());
     if (patternDocument == null) {
       return;
     }
index 07cd46cfdf200e8249564d2b0518d2c4007b9ee6..9ac18ba159a3ab9bd21e565bf9fe0287e76d53ec 100644 (file)
@@ -24,7 +24,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase {
       return;
     }
     String name = file.getName();
-    VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, name);
+    VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, name);
     ApplicationManager.getApplication().runWriteAction(() -> {
       if (patternFile != null) {
         try {
index 70219b1442b9b752292e3269f7e20dc1a6c2acb0..d950bc814f9b067e8b13a3487291868e42a49789 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 0ee3584adfd6aa91907ae9f4e2bd8ec38bd08abe..e017f69e13c0b9427721a67122432a997aa2b158 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);
@@ -276,4 +279,38 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
   public void setTurnEditingMode(boolean turnEditingMode) {
     myTurnEditingMode = turnEditingMode;
   }
+
+  public String getLogin() {
+    if (myUser != null) {
+      return myUser.getEmail();
+    }
+    return "";
+  }
+
+  public String getPassword() {
+    if (myUser != null) {
+      return myUser.getPassword();
+    }
+    return "";
+  }
+
+  public void setLogin(String login) {
+    if (myUser != null) {
+      myUser.setEmail(login);
+    }
+  }
+
+  public void setPassword(String password) {
+    if (myUser != null) {
+      myUser.setPassword(password);
+    }
+  }
+
+  public StepicUser getUser() {
+    return myUser;
+  }
+
+  public void setUser(StepicUser user) {
+    myUser = user;
+  }
 }
index 78f8a7133005f9790976d1dc9202ecec48c19573..175007d52afcc5646cd1132f21fe2ec907b1a0d3 100644 (file)
@@ -20,7 +20,9 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileEditor.FileEditor;
 import com.intellij.openapi.fileEditor.FileEditorManager;
 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
+import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.ui.popup.Balloon;
 import com.intellij.openapi.util.Disposer;
@@ -36,6 +38,7 @@ import com.intellij.psi.PsiFile;
 import com.intellij.ui.JBColor;
 import com.intellij.ui.awt.RelativePoint;
 import com.intellij.ui.content.Content;
+import com.intellij.util.TimeoutUtil;
 import com.intellij.util.ObjectUtils;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.text.MarkdownUtil;
@@ -47,6 +50,7 @@ import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
 import com.jetbrains.edu.learning.editor.StudyEditor;
 import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
@@ -61,6 +65,9 @@ import java.io.*;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 public class StudyUtils {
   private StudyUtils() {
@@ -358,12 +365,12 @@ public class StudyUtils {
 
 
   @Nullable
-  public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) {
+  public static VirtualFile getPatternFile(@NotNull Project project, @NotNull TaskFile taskFile, String name) {
     Task task = taskFile.getTask();
     String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex());
     String taskDir = EduNames.TASK + String.valueOf(task.getIndex());
     Course course = task.getLesson().getCourse();
-    File resourceFile = new File(course.getCourseDirectory());
+    File resourceFile = getCourseDirectory(project, course);
     if (!resourceFile.exists()) {
       return null;
     }
@@ -376,8 +383,8 @@ public class StudyUtils {
   }
 
   @Nullable
-  public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) {
-    VirtualFile patternFile = getPatternFile(taskFile, name);
+  public static Document getPatternDocument(@NotNull Project project, @NotNull final TaskFile taskFile, String name) {
+    VirtualFile patternFile = getPatternFile(project, taskFile, name);
     if (patternFile == null) {
       return null;
     }
@@ -495,7 +502,7 @@ public class StudyUtils {
     return null;
   }
 
-
+  @Nullable
   public static String getTaskText(@NotNull final Project project) {
     TaskFile taskFile = getSelectedTaskFile(project);
     if (taskFile == null) {
@@ -519,12 +526,33 @@ public class StudyUtils {
     }
     return taskFile;
   }
+  
+  @Nullable
+  public static Task getCurrentTask(@NotNull final Project project) {
+    VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles();
+    TaskFile taskFile = null;
+    for (VirtualFile file : files) {
+      taskFile = getTaskFile(project, file);
+      if (taskFile != null) {
+        break;
+      }
+    }
+    if (taskFile != null) {
+      return taskFile.getTask();
+    }
+    return null;
+  }
 
   public static void updateStudyToolWindow(Project project) {
     final StudyToolWindow studyToolWindow = getStudyToolWindow(project);
     if (studyToolWindow != null) {
       String taskText = getTaskText(project);
-      studyToolWindow.setTaskText(taskText, null, project);
+      if (taskText != null) {
+        studyToolWindow.setTaskText(taskText, null, project);
+      }
+      else {
+        LOG.warn("Task text is null");
+      }
     }
   }
 
@@ -532,6 +560,32 @@ public class StudyUtils {
     return StudyTaskManager.getInstance(project).getCourse() != null;
   }
 
+  @Nullable
+  public static Project getStudyProject() {
+    Project studyProject = null;
+    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
+    for (Project project : openProjects) {
+      if (StudyTaskManager.getInstance(project).getCourse() != null) {
+         studyProject = project;
+      }
+    }
+    return studyProject;
+  }
+
+  @NotNull
+  public static File getCourseDirectory(@NotNull Project project, Course course) {
+    final File courseDirectory;
+    if (course.isAdaptive()) {
+      courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR,
+                                 StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName()
+                                 + "_" + StudyTaskManager.getInstance(project).getUser().getEmail());
+    }
+    else {
+      courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName());
+    }
+    return courseDirectory;
+  }
+
   public static boolean hasJavaFx() {
     try {
       Class.forName("javafx.application.Platform");
@@ -575,6 +629,36 @@ public class StudyUtils {
     return null;
   }
 
+  // supposd to be called under progress
+  @Nullable
+  public static <T> T execCancelable(@NotNull final Callable<T> callable) {
+    final Future<T> future = ApplicationManager.getApplication().executeOnPooledThread(callable);
+
+    while (!future.isCancelled() && !future.isDone()) {
+      ProgressManager.checkCanceled();
+      TimeoutUtil.sleep(500);
+    }
+    T result = null;
+    try {
+      result = future.get();
+    }
+    catch (InterruptedException | ExecutionException e) {
+      LOG.warn(e.getMessage());
+    }
+    return result;
+  }
+
+  @Nullable
+  public static Task getTaskFromSelectedEditor(Project project) {
+    final StudyEditor editor = getSelectedStudyEditor(project);
+    Task task = null;
+    if (editor != null) {
+      final TaskFile file = editor.getTaskFile();
+      task = file.getTask();
+    }
+    return task;
+  }
+
   private static String convertToHtml(@NotNull final String content) {
     ArrayList<String> lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n"));
     MarkdownUtil.replaceHeaders(lines);
index fc3acfd7870afd861f7b937e454a2e29c0fb74d6..b05088812f2c250323164f92ef7eac7b2e614821 100644 (file)
@@ -45,7 +45,7 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
     }
     StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project);
     final StudyState studyState = new StudyState(studyEditor);
-    Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
+    Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName());
     if (patternDocument == null) {
       return;
     }
index b19b56a3578dbb6bd66d034a09950072460a6d2c..cb47386d2a4bf3c61f5d8bfd0faff8b50201159d 100644 (file)
@@ -81,7 +81,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
                                        @NotNull final Project project,
                                        TaskFile taskFile,
                                        String name) {
-    if (!resetDocument(document, taskFile, name)) {
+    if (!resetDocument(project, document, taskFile, name)) {
       return false;
     }
     taskFile.getTask().setStatus(StudyStatus.Unchecked);
@@ -110,10 +110,11 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut {
   }
 
 
-  private static boolean resetDocument(@NotNull final Document document,
+  private static boolean resetDocument(@NotNull final Project project, 
+                                       @NotNull final Document document, 
                                        @NotNull final TaskFile taskFile,
                                        String fileName) {
-    final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName);
+    final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName);
     if (patternDocument == null) {
       return false;
     }
index f8302304b9b09f4524dcde9ed73d71d1d208fee1..965347321167aa794bd6913756c4dc759f9d957f 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,57 +73,132 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
 
   @Override
   public void run(@NotNull ProgressIndicator indicator) {
+    final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+    if (course != null && course.isAdaptive()) {
+      checkForAdaptiveCourse(indicator);
+    }
+    else {
+      checkForEduCourse(indicator);
+    }
+  }
+
+  private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
+    final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
+    
+    if (testsOutput != null) {
+      if (testsOutput.isSuccess()) {
+        onTaskSolved(testsOutput.getMessage());
+      }
+      else {
+        onTaskFailed(testsOutput.getMessage());
+      }
+      runAfterTaskCheckedActions();
+      postAttemptToStepic(testsOutput);
+    }
+  }
+
+  @Nullable
+  private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
     final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
     final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
     if (indicator.isCanceled()) {
       ApplicationManager.getApplication().invokeLater(
         () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
-      return;
     }
 
-
-    final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output);
-    String stderr = output.getStderr();
-    if (!stderr.isEmpty()) {
-      ApplicationManager.getApplication().invokeLater(() ->
-                                                        StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
-                                                                                            MessageType.WARNING.getPopupBackground(),
-                                                                                            myProject));
-      //log error output of tests
-      LOG.info("#educational " + stderr);
-      return;
+    final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+    if (course != null) {
+      final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
+      String stderr = output.getStderr();
+      if (!stderr.isEmpty()) {
+        ApplicationManager.getApplication().invokeLater(() ->
+                                                          StudyCheckUtils.showTestResultPopUp("Failed to launch checking",
+                                                                                              MessageType.WARNING.getPopupBackground(),
+                                                                                              myProject));
+        //log error output of tests
+        LOG.info("#educational " + stderr);
+        return null;
+      }
+      return testsOutput;
     }
+    return null;
+  }
 
-    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));
+    final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+
+    if (course != null) {
+      if (course.isAdaptive()) {
+        ApplicationManager.getApplication().invokeLater(
+          () -> {
+            StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
+            StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
+          });
+      }
+      else {
+        ApplicationManager.getApplication()
+          .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
+      }
+    }
   }
 
-  protected void onTaskSolved(StudyTestsOutputParser.TestsOutput testsOutput) {
+  protected void onTaskSolved(String message) {
     myTaskManger.setStatus(myTask, StudyStatus.Solved);
-    ApplicationManager.getApplication().invokeLater(
-      () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), myProject));
+    final Course course = StudyTaskManager.getInstance(myProject).getCourse();
+
+    if (course != null) {
+      if (course.isAdaptive()) {
+        ApplicationManager.getApplication().invokeLater(
+          () -> {
+            StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
+            StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
+          });
+      }
+      else {
+        ApplicationManager.getApplication()
+          .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject));
+      }
+    }
   }
 
-  private void runAfterTaskSolvedActions() {
+  private void runAfterTaskCheckedActions() {
     StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
     if (configurator != null) {
       StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
       if (checkActions != null) {
-        for (StudyAfterCheckAction action: checkActions) {
+        for (StudyAfterCheckAction action : checkActions) {
           action.run(myProject, myTask, myStatusBeforeCheck);
         }
       }
@@ -130,8 +208,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
     }
   }
 
-  protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) {
-    final StudySettings studySettings = StudySettings.getInstance();
+  protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) {
+    final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject);
     final String login = studySettings.getLogin();
     final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword();
     EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password);
index b1626645bf7e1cdbba206828b8f2ac168bece747..b91bc4773ba4e15190a3fa6faeac5ff592cd871c 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 {
 
@@ -191,4 +193,31 @@ public class StudyCheckUtils {
       EduUtils.flushWindows(taskFile, virtualFile, true);
     }
   }
+
+  public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message, boolean solved) {
+    final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
+    ToolWindow window = toolWindowManager.getToolWindow("Test Results");
+    if (window == null) {
+      toolWindowManager.registerToolWindow("Test Results", true, ToolWindowAnchor.BOTTOM);
+      window = toolWindowManager.getToolWindow("Test Results");
+      new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window);
+    }
+
+    final Content[] contents = window.getContentManager().getContents();
+    for (Content content : contents) {
+      final JComponent component = content.getComponent();
+      if (component instanceof PythonConsoleView) {
+        ((PythonConsoleView)component).clear();
+        if (!solved) {
+          ((PythonConsoleView)component).print(message, ConsoleViewContentType.ERROR_OUTPUT);
+        }
+        else {
+          ((PythonConsoleView)component).print(message, ConsoleViewContentType.NORMAL_OUTPUT);
+        }
+        window.setAvailable(true, () -> {});
+        window.show(() -> {});
+        return;
+      }
+    }
+  }
 }
index 7bea87d39e5174c47a5a9fb3023ef9e81745d8bd..c75904d0e3a54cb0a162f666aedcc1be3ca9f540 100644 (file)
@@ -14,6 +14,7 @@ import com.jetbrains.edu.learning.core.EduDocumentListener;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.StudyTaskManager;
@@ -65,14 +66,17 @@ public class StudySmartChecker {
         Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath());
         final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath());
         final ProcessOutput output = handler.runProcess();
-        boolean res = StudyTestsOutputParser.getTestsOutput(output).isSuccess();
-        StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
-        StudyUtils.deleteFile(windowCopy);
-        if (fileWindows != null) {
-          StudyUtils.deleteFile(fileWindows);
-        }
-        if (!resourceFile.delete()) {
-          LOG.error("failed to delete", resourceFile.getPath());
+        final Course course = StudyTaskManager.getInstance(project).getCourse();
+        if (course != null) {
+          boolean res = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()).isSuccess();
+          StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed);
+          StudyUtils.deleteFile(windowCopy);
+          if (fileWindows != null) {
+            StudyUtils.deleteFile(fileWindows);
+          }
+          if (!resourceFile.delete()) {
+            LOG.error("failed to delete", resourceFile.getPath());
+          }
         }
       }
     }
index 55bfe7273bac72814873e355c6a1b0bf89d83efe..90cf27d4c9584501a77d57a0bfe4e67fe88f5cc5 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 ";
@@ -29,10 +31,12 @@ public class StudyTestsOutputParser {
   }
 
   @NotNull
-  public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) {
+  public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) {
     String congratulations = CONGRATULATIONS;
-    for (String line : processOutput.getStdoutLines()) {
-      if (line.startsWith(ourStudyPrefix)) {
+    final List<String> lines = processOutput.getStdoutLines();
+    for (int i = 0; i < lines.size(); i++) {
+      final String line = lines.get(i);
+      if (line.startsWith(STUDY_PREFIX)) {
         if (line.contains(TEST_OK)) {
           continue;
         }
@@ -42,7 +46,23 @@ public class StudyTestsOutputParser {
         }
 
         if (line.contains(TEST_FAILED)) {
-          return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
+          if (!isAdaptive) {
+            return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()));
+          }
+          else {
+            final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n");
+            for (int j = i + 1; j < lines.size(); j++) {
+              final String failedTextLine = lines.get(j);
+              if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) {
+                builder.append(failedTextLine);
+                builder.append("\n");
+              }
+              else {
+                break;
+              }
+            }
+            return new TestsOutput(false, builder.toString());
+          }          
         }
       }
     }
index 16201112691646e3be6200f9d9ed9696b6e315b8..03dbc110882c5f6d523a4f212f7b324887bc2f9b 100644 (file)
@@ -7,31 +7,27 @@ import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.Function;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.stepic.CourseInfo;
+import com.jetbrains.edu.learning.stepic.StepicUser;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class Course {
-  @Expose
-  private List<Lesson> lessons = new ArrayList<Lesson>();
-
+  @Expose private List<Lesson> lessons = new ArrayList<Lesson>();
+  @Expose private List<StepicUser> authors = new ArrayList<StepicUser>();
   @Expose private String description;
   @Expose private String name;
-  private String myCourseDirectory = "";
-  @Expose private List<CourseInfo.Author> authors = new ArrayList<CourseInfo.Author>();
-  private boolean myUpToDate;
-
-  @Expose @SerializedName("language")
-  private String myLanguage = "Python";
+  @Expose private String myCourseDirectory = "";
+  @Expose private int id;
+  @Expose private boolean myUpToDate;
+  @Expose private boolean isAdaptive;
+  @Expose @SerializedName("language") private String myLanguage = "Python";
 
-  //this field is used to distinguish ordinary and CheckIO projects
+  //this field is used to distinguish ordinary and CheckIO projects,
   //"PyCharm" is used here for historical reasons
-  private String courseType = EduNames.PYCHARM;
-
-  //this field is used to distinguish study and course creator modes
-  private String courseMode = EduNames.STUDY;
+  @Expose private String courseType = EduNames.PYCHARM;
+  @Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
 
   /**
    * Initializes state of course
@@ -72,20 +68,20 @@ public class Course {
   }
 
   @NotNull
-  public List<CourseInfo.Author> getAuthors() {
+  public List<StepicUser> getAuthors() {
     return authors;
   }
 
-  public static String getAuthorsString(@NotNull List<CourseInfo.Author> authors) {
+  public static String getAuthorsString(@NotNull List<StepicUser> authors) {
     return StringUtil.join(authors, author -> author.getName(), ", ");
   }
 
   public void setAuthors(String[] authors) {
-    this.authors = new ArrayList<CourseInfo.Author>();
+    this.authors = new ArrayList<StepicUser>();
     for (String name : authors) {
       final List<String> pair = StringUtil.split(name, " ");
       if (!pair.isEmpty())
-        this.authors.add(new CourseInfo.Author(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
+        this.authors.add(new StepicUser(pair.get(0), pair.size() > 1 ? pair.get(1) : ""));
     }
   }
 
@@ -133,7 +129,7 @@ public class Course {
     myLanguage = language;
   }
 
-  public void setAuthors(List<CourseInfo.Author> authors) {
+  public void setAuthors(List<StepicUser> authors) {
     this.authors = authors;
   }
 
@@ -146,6 +142,22 @@ public class Course {
     this.courseType = courseType;
   }
 
+  public boolean isAdaptive() {
+    return isAdaptive;
+  }
+
+  public void setAdaptive(boolean adaptive) {
+    isAdaptive = adaptive;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  public void setId(int id) {
+    this.id = id;
+  }
+
   public String getCourseMode() {
     return courseMode;
   }
index 5b2f6acf9f225c032fac5b25dee36d369a7e6db1..4110dd621c28d800c6d0fb8079deeaea0aceba08 100644 (file)
@@ -11,25 +11,13 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class Lesson implements StudyItem {
-  public int id;
-  @Transient
-  public List<Integer> steps;
-  @Transient
-  public List<String> tags;
-  @Transient
-  Boolean is_public;
-  @Expose
-  @SerializedName("title")
-  private String name;
-  @Expose
-  @SerializedName("task_list")
-  public List<Task> taskList = new ArrayList<Task>();
-
-  @Transient
-  private Course myCourse = null;
-
-  // index is visible to user number of lesson from 1 to lesson number
-  private int myIndex = -1;
+  @Expose public int id;
+  @Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number
+  @Expose @Transient public List<Integer> steps;
+  @Expose @SerializedName("title") private String name;
+  @Expose @SerializedName("task_list") public List<Task> taskList = new ArrayList<Task>();
+  @Transient public List<String> tags;
+  @Transient private Course myCourse = null;
 
   public void initLesson(final Course course, boolean isRestarted) {
     setCourse(course);
index 71ca327a45be9b2d3d1aafa42a987d4f895c4aef..8cb75e13ee3c55ed60a66e9a832a65f589995aa5 100644 (file)
@@ -20,22 +20,13 @@ import java.util.Map;
  * Implementation of task which contains task files, tests, input file for tests
  */
 public class Task implements StudyItem {
-  @Expose
-  private String name;
-
-  // index is visible to user number of task from 1 to task number
-  private int myIndex;
-  private StudyStatus myStatus = StudyStatus.Uninitialized;
-
-  private int myStepicId;
-
-  @Expose
-  @SerializedName("task_files")
-  public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
-
-  private String text;
-  private Map<String, String> testsText = new HashMap<String, String>();
-
+  @Expose private String name;
+  @Expose private String text;
+  @Expose private int myStepicId;
+  @Expose private int myIndex; // index is visible to user number of task from 1 to task number
+  @Expose @SerializedName("task_files") public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
+  @Expose private Map<String, String> testsText = new HashMap<String, String>();
+  @Expose private StudyStatus myStatus = StudyStatus.Uninitialized;
   @Transient private Lesson myLesson;
 
   public Task() {}
index 33e00d574325d7b1c0a1f415a956845b65e9a09b..65ef81d5e6b53b8902692c8cf1d256d5dc2ecb6c 100644 (file)
@@ -18,19 +18,14 @@ import java.util.List;
  */
 
 public class TaskFile {
-  @SerializedName("placeholders")
-  @Expose
-  private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
-  private int myIndex = -1;
-
-  @Expose
-  public String name;
-  @Expose
-  public String text;
+  @Expose public String name;
+  @Expose public String text;
+  @Expose private int myIndex = -1;
+  @Expose private boolean myUserCreated = false;
+  @Expose private boolean myTrackChanges = true;
+  @Expose private boolean myHighlightErrors = false;
+  @Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
   @Transient private Task myTask;
-  private boolean myUserCreated = false;
-  private boolean myTrackChanges = true;
-  private boolean myHighlightErrors = false;
 
   public void initTaskFile(final Task task, boolean isRestarted) {
     setTask(task);
index 57b84b9f6df1de65db6f85ebb209bcfccbddb32f..6e031380977b1b9021ddbdcb20bf089f1d3ff156 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;
@@ -106,30 +106,30 @@ public class StudyGenerator {
   public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot,
                                   @NotNull final Project project) {
 
-              try {
-                final List<Lesson> lessons = course.getLessons();
-                for (int i = 1; i <= lessons.size(); i++) {
-                  Lesson lesson = lessons.get(i - 1);
-                  lesson.setIndex(i);
-                  createLesson(lesson, baseDir, resourceRoot, project);
-                }
-                baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
-                File[] files = resourceRoot.listFiles(
-                  (dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
-                for (File file : files) {
-                  File dir = new File(baseDir.getPath(), file.getName());
-                  if (file.isDirectory()) {
-                    FileUtil.copyDir(file, dir);
-                    continue;
-                  }
-
-                  FileUtil.copy(file, dir);
-
-                }
-              }
-              catch (IOException e) {
-                LOG.error(e);
-              }
+    try {
+      final List<Lesson> lessons = course.getLessons();
+      for (int i = 1; i <= lessons.size(); i++) {
+        Lesson lesson = lessons.get(i - 1);
+        lesson.setIndex(i);
+        createLesson(lesson, baseDir, resourceRoot, project);
+      }
+      baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR);
+      File[] files = resourceRoot.listFiles(
+        (dir, name) -> !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS));
+      for (File file : files) {
+        File dir = new File(baseDir.getPath(), file.getName());
+        if (file.isDirectory()) {
+          FileUtil.copyDir(file, dir);
+          continue;
+        }
+  
+        FileUtil.copy(file, dir);
+  
+      }
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
   }
 
 }
index d87b5be1b4fa9a0807097949e8bd06269bf37ae2..2200c7b791cbb989b9808316e0c9c6c079fe7c22 100644 (file)
@@ -8,10 +8,12 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.DumbModePermission;
 import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.ThrowableComputable;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -22,38 +24,40 @@ import com.intellij.platform.templates.github.ZipUtil;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
 import com.intellij.util.containers.ContainerUtil;
+import com.jetbrains.edu.learning.StudyProjectComponent;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.Lesson;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyProjectComponent;
-import com.jetbrains.edu.learning.StudyTaskManager;
-import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.stepic.CourseInfo;
 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
+import com.jetbrains.edu.learning.stepic.StepicUser;
 import org.apache.commons.codec.binary.Base64;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+
+import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
 
 public class StudyProjectGenerator {
+  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 +68,67 @@ public class StudyProjectGenerator {
   }
 
   public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) {
-    final Course course = getCourse();
+    StudyTaskManager.getInstance(project).setUser(myUser);
+    final Course course = getCourse(project);
     if (course == null) {
       LOG.warn("Course is null");
       return;
     }
+    final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
     StudyTaskManager.getInstance(project).setCourse(course);
     ApplicationManager.getApplication().invokeLater(
       () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND,
                                                     () -> ApplicationManager.getApplication().runWriteAction(() -> {
-                                                      course.initCourse(false);
-                                                      final File courseDirectory = new File(ourCoursesDir, course.getName());
                                                       StudyGenerator.createCourse(course, baseDir, courseDirectory, project);
-                                                      course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath());
+                                                      course.setCourseDirectory(courseDirectory.getAbsolutePath());
                                                       VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
                                                       StudyProjectComponent.getInstance(project).registerStudyToolWindow(course);
                                                       openFirstTask(course, project);
                                                     })));
   }
 
-  protected Course getCourse() {
+  @Nullable
+  protected Course getCourse(@NotNull final Project project) {
+    final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
+    if (courseFile.exists()) {
+      return readCourseFromCache(courseFile, false);
+    }
+    else if (myUser != null) {
+      final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX +
+                                                                         mySelectedCourseInfo.getName() + "_" +
+                                                                         myUser.getEmail()), EduNames.COURSE_META_FILE);
+      if (adaptiveCourseFile.exists()) {
+        return readCourseFromCache(adaptiveCourseFile, true);
+      }
+    }
+    final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo);
+    if (course != null) {
+      flushCourse(project, course);
+      course.initCourse(false);
+    }
+    return course;
+  }
+
+  @Nullable
+  private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) {
     Reader reader = null;
     try {
-      final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE);
-      if (courseFile.exists()) {
-        reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
-        Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
-        final Course course = gson.fromJson(reader, Course.class);
-        course.initCourse(false);
-        return course;
-      }
+      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 (FileNotFoundException | UnsupportedEncodingException e) {
-      LOG.error(e);
+    catch (UnsupportedEncodingException e) {
+      LOG.warn(e.getMessage());
+    }
+    catch (FileNotFoundException e) {
+      LOG.warn(e.getMessage());
     }
     finally {
       StudyUtils.closeSilently(reader);
     }
-    final Course course = EduStepicConnector.getCourse(mySelectedCourseInfo);
-    if (course != null) {
-      flushCourse(course);
-    }
-    return course;
+    return null;
   }
 
   public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) {
@@ -131,7 +154,8 @@ public class StudyProjectGenerator {
       final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile);
       ProjectView.getInstance(project).select(file, activeVirtualFile, true);
       FileEditorManager.getInstance(project).openFile(activeVirtualFile, true);
-    } else {
+    }
+    else {
       String first = StudyUtils.getFirst(taskFiles.keySet());
       if (first != null) {
         NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first);
@@ -142,8 +166,8 @@ public class StudyProjectGenerator {
     }
   }
 
-  public void flushCourse(@NotNull final Course course) {
-    final File courseDirectory = new File(ourCoursesDir, course.getName());
+  public static void flushCourse(@NotNull final Project project, @NotNull final Course course) {
+    final File courseDirectory = StudyUtils.getCourseDirectory(project, course);
     FileUtil.createDirectory(courseDirectory);
     flushCourseJson(course, courseDirectory);
 
@@ -208,7 +232,6 @@ public class StudyProjectGenerator {
         else {
           FileUtil.writeToFile(file, taskFile.text);
         }
-
       }
       catch (IOException e) {
         LOG.error("ERROR copying file " + name);
@@ -217,9 +240,12 @@ public class StudyProjectGenerator {
     final Map<String, String> testsText = task.getTestsText();
     for (Map.Entry<String, String> entry : testsText.entrySet()) {
       final File testsFile = new File(taskDirectory, entry.getKey());
+      if (testsFile.exists()) {
+        FileUtil.delete(testsFile);
+      }
       FileUtil.createIfDoesntExist(testsFile);
       try {
-          FileUtil.writeToFile(testsFile, entry.getValue());
+        FileUtil.writeToFile(testsFile, entry.getValue());
       }
       catch (IOException e) {
         LOG.error("ERROR copying tests file");
@@ -235,8 +261,8 @@ public class StudyProjectGenerator {
     }
   }
 
-  private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
-    final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+  public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) {
+    final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
     final String json = gson.toJson(course);
     final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE);
     final FileOutputStream fileOutputStream;
@@ -269,7 +295,7 @@ public class StudyProjectGenerator {
    */
   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
   public static void flushCache(List<CourseInfo> courses) {
-    File cacheFile = new File(ourCoursesDir, CACHE_NAME);
+    File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
     PrintWriter writer = null;
     try {
       if (!createCacheFile(cacheFile)) return;
@@ -290,8 +316,8 @@ public class StudyProjectGenerator {
   }
 
   private static boolean createCacheFile(File cacheFile) throws IOException {
-    if (!ourCoursesDir.exists()) {
-      final boolean created = ourCoursesDir.mkdirs();
+    if (!OUR_COURSES_DIR.exists()) {
+      final boolean created = OUR_COURSES_DIR.mkdirs();
       if (!created) {
         LOG.error("Cannot flush courses cache. Can't create courses directory");
         return false;
@@ -307,12 +333,13 @@ public class StudyProjectGenerator {
     return true;
   }
 
-  public List<CourseInfo> getCourses(boolean force) {
-    if (ourCoursesDir.exists()) {
+  // Supposed to be called under progress
+  public List<CourseInfo> getCoursesAsynchronouslyIfNeeded(boolean force) {
+    if (OUR_COURSES_DIR.exists()) {
       myCourses = getCoursesFromCache();
     }
     if (force || myCourses.isEmpty()) {
-      myCourses = EduStepicConnector.getCourses();
+      myCourses = execCancelable(EduStepicConnector::getCourses);
       flushCache(myCourses);
     }
     if (myCourses.isEmpty()) {
@@ -321,6 +348,22 @@ public class StudyProjectGenerator {
     return myCourses;
   }
 
+  public List<CourseInfo> getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) {
+    try {
+      return ProgressManager.getInstance()
+        .runProcessWithProgressSynchronously(new ThrowableComputable<List<CourseInfo>, RuntimeException>() {
+          @Override
+          public List<CourseInfo> compute() throws RuntimeException {
+            ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
+            return getCoursesAsynchronouslyIfNeeded(force);
+          }
+        }, progressTitle, true, project);
+    }
+    catch (RuntimeException e) {
+      return Collections.singletonList(CourseInfo.INVALID_COURSE);
+    }
+  }
+
   public void addSettingsStateListener(@NotNull SettingsListener listener) {
     myListeners.add(listener);
   }
@@ -336,7 +379,7 @@ public class StudyProjectGenerator {
   }
 
   public static List<CourseInfo> getBundledIntro() {
-    final File introCourse = new File(ourCoursesDir, "Introduction to Python");
+    final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python");
     if (introCourse.exists()) {
       final CourseInfo courseInfo = getCourseInfo(introCourse);
 
@@ -347,7 +390,7 @@ public class StudyProjectGenerator {
 
   public static List<CourseInfo> getCoursesFromCache() {
     List<CourseInfo> courses = new ArrayList<>();
-    final File cacheFile = new File(ourCoursesDir, CACHE_NAME);
+    final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME);
     if (!cacheFile.exists()) {
       return courses;
     }
@@ -360,7 +403,9 @@ public class StudyProjectGenerator {
           while ((line = reader.readLine()) != null) {
             Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
             final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class);
-            courses.add(courseInfo);
+            if (!courseInfo.isAdaptive()) {
+              courses.add(courseInfo);
+            }
           }
         }
         catch (IOException | JsonSyntaxException e) {
@@ -369,7 +414,8 @@ public class StudyProjectGenerator {
         finally {
           StudyUtils.closeSilently(reader);
         }
-      } finally {
+      }
+      finally {
         StudyUtils.closeSilently(inputStream);
       }
     }
@@ -378,6 +424,7 @@ public class StudyProjectGenerator {
     }
     return courses;
   }
+
   /**
    * Adds course from zip archive to courses
    *
@@ -389,12 +436,14 @@ public class StudyProjectGenerator {
     try {
       String fileName = file.getName();
       String unzippedName = fileName.substring(0, fileName.indexOf("."));
-      File courseDir = new File(ourCoursesDir, unzippedName);
+      File courseDir = new File(OUR_COURSES_DIR, unzippedName);
       ZipUtil.unzip(null, courseDir, file, null, null, true);
       CourseInfo courseName = addCourse(myCourses, courseDir);
       flushCache(myCourses);
       if (courseName != null && !courseName.getName().equals(unzippedName)) {
-        courseDir.renameTo(new File(ourCoursesDir, courseName.getName()));
+        //noinspection ResultOfMethodCallIgnored
+        courseDir.renameTo(new File(OUR_COURSES_DIR, courseName.getName()));
+        //noinspection ResultOfMethodCallIgnored
         courseDir.delete();
       }
       return courseName;
@@ -409,7 +458,6 @@ public class StudyProjectGenerator {
   /**
    * Adds course to courses specified in params
    *
-   *
    * @param courses
    * @param courseDir must be directory containing course file
    * @return added course name or null if course is invalid
@@ -431,6 +479,7 @@ public class StudyProjectGenerator {
     }
     return null;
   }
+
   /**
    * Parses course json meta file and finds course name
    *
@@ -462,10 +511,13 @@ public class StudyProjectGenerator {
         courseInfo.setName(courseName);
         courseInfo.setDescription(courseDescription);
         courseInfo.setType("pycharm " + language);
-        final ArrayList<CourseInfo.Author> authors = new ArrayList<>();
+        final ArrayList<StepicUser> authors = new ArrayList<>();
         for (JsonElement author : courseAuthors) {
           final JsonObject authorAsJsonObject = author.getAsJsonObject();
-          authors.add(new CourseInfo.Author(authorAsJsonObject.get("first_name").getAsString(), authorAsJsonObject.get("last_name").getAsString()));
+          final StepicUser stepicUser = new StepicUser();
+          stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString());
+          stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString());
+          authors.add(stepicUser);
         }
         courseInfo.setAuthors(authors);
       }
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..c8c0c90aaf0480ad80e835330be523892a816934 100644 (file)
@@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic;
 import com.google.gson.annotations.SerializedName;
 import com.intellij.openapi.util.text.StringUtil;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -12,29 +13,26 @@ import java.util.List;
  * and when project is being created
  */
 public class CourseInfo {
-  boolean is_public;
-  public List<Integer> sections;
-  @SerializedName("title")
-  private String myName;
-  @SerializedName("summary")
-  private String myDescription;
-  @SerializedName("course_format")
-  //course type in format "pycharm <language>"
-  private String myType = "pycharm Python";
-
-  List<Integer> instructors = new ArrayList<Integer>();
+  public static CourseInfo INVALID_COURSE = new CourseInfo();
 
-  List<Author> myAuthors = new ArrayList<Author>();
+  @SerializedName("title") private String myName;
   int id;
+  boolean isAdaptive;
+  boolean isPublic;
+  List<Integer> sections;
+  List<Integer> instructors = new ArrayList<Integer>();
 
-  public static CourseInfo INVALID_COURSE = new CourseInfo();
+  List<StepicUser> myAuthors = new ArrayList<>();
+  @SerializedName("summary") private String myDescription;
+  @SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm <language>"
+  @Nullable private String username;
 
   public String getName() {
     return myName;
   }
 
   @NotNull
-  public List<Author> getAuthors() {
+  public List<StepicUser> getAuthors() {
     return myAuthors;
   }
 
@@ -48,7 +46,7 @@ public class CourseInfo {
 
   @Override
   public String toString() {
-    return myName;
+    return getName();
   }
 
   @Override
@@ -57,17 +55,26 @@ public class CourseInfo {
     if (o == null || getClass() != o.getClass()) return false;
     CourseInfo that = (CourseInfo)o;
     if (that.getName() == null || that.getDescription() == null) return false;
-    return that.getName().equals(myName)
+    return that.getName().equals(getName())
            && that.getDescription().equals(myDescription);
   }
 
   @Override
   public int hashCode() {
-    int result = myName != null ? myName.hashCode() : 0;
+    int result = getName() != null ? getName().hashCode() : 0;
     result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
     return result;
   }
 
+  @Nullable
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(@Nullable String username) {
+    this.username = username;
+  }
+
   public static class Author {
     int id;
     String first_name = "";
@@ -93,18 +100,18 @@ public class CourseInfo {
     myName = name;
   }
 
-  public void setAuthors(List<Author> authors) {
+  public void setAuthors(List<StepicUser> authors) {
     myAuthors = authors;
-    for (Author author : authors) {
+    for (StepicUser author : authors) {
       if (author.id > 0) {
         instructors.add(author.id);
       }
     }
   }
 
-  public void addAuthor(Author author) {
+  public void addAuthor(StepicUser author) {
     if (myAuthors == null) {
-      myAuthors = new ArrayList<Author>();
+      myAuthors = new ArrayList<>();
     }
     myAuthors.add(author);
   }
@@ -116,4 +123,20 @@ public class CourseInfo {
   public void setType(String type) {
     myType = type;
   }
+
+  public boolean isAdaptive() {
+    return isAdaptive;
+  }
+
+  public void setAdaptive(boolean adaptive) {
+    isAdaptive = adaptive;
+  }
+
+  public boolean isPublic() {
+    return isPublic;
+  }
+
+  public void setPublic(boolean aPublic) {
+    isPublic = aPublic;
+  }
 }
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..3da3bfc
--- /dev/null
@@ -0,0 +1,511 @@
+package com.jetbrains.edu.learning.stepic;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.checker.StudyExecutor;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.*;
+import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
+import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
+import com.jetbrains.edu.learning.editor.StudyEditor;
+import com.jetbrains.edu.learning.ui.StudyToolWindow;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
+
+public class EduAdaptiveStepicConnector {
+  public static final String PYTHON27 = "python27";
+  public static final String PYTHON3 = "python3";
+
+  private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
+  private static final String STEPIC_URL = "https://stepic.org/";
+  private static final String STEPIC_API_URL = STEPIC_URL + "api/";
+  private static final String RECOMMENDATIONS_URL = "recommendations";
+  private static final String CONTENT_TYPE_APPL_JSON = "application/json";
+  private static final String LESSON_URL = "lessons/";
+  private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions";
+  private static final String ATTEMPTS_URL = "attempts";
+  private static final String SUBMISSION_URL = "submissions";
+  private static final String ASSIGNMENT_URL = "/assignments";
+  private static final String VIEWS_URL = "/views";
+  private static final String UNITS_URL = "/units";
+  private static final String DEFAULT_TASKFILE_NAME = "code.py";
+
+  @Nullable
+  public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
+    try {
+      final CloseableHttpClient client = getHttpClient(project);
+      final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL)
+        .addParameter("course", String.valueOf(course.getId()))
+        .build();
+      final HttpGet request = new HttpGet(uri);
+      setHeaders(request, CONTENT_TYPE_APPL_JSON);
+
+      final CloseableHttpResponse response = client.execute(request);
+      final StatusLine statusLine = response.getStatusLine();
+      final HttpEntity responseEntity = response.getEntity();
+      final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
+
+      if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
+        final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+        final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
+
+        if (recomWrapper.recommendations.length != 0) {
+          final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
+          final String lessonId = recommendation.lesson;
+          final StepicWrappers.LessonContainer
+            lessonContainer = getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class);
+          if (lessonContainer.lessons.size() == 1) {
+            final Lesson realLesson = lessonContainer.lessons.get(0);
+            course.getLessons().get(0).id = Integer.parseInt(lessonId);
+
+            viewAllSteps(client, realLesson.id);
+
+            for (int stepId : realLesson.steps) {
+              final StepicWrappers.Step step = getStep(stepId);
+              if (step.name.equals("code")) {
+                return getTaskFromStep(project, stepId, step, realLesson.getName());
+              }
+            }
+
+            LOG.warn("Got a lesson without code part as a recommendation");
+          }
+          else {
+            LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
+          }
+        }
+      }
+      else {
+        throw new IOException("Stepic returned non 200 status code: " + responseString);
+      }
+    }
+    catch (IOException e) {
+      LOG.warn(e.getMessage());
+    }
+    catch (URISyntaxException e) {
+      LOG.warn(e.getMessage());
+    }
+    return null;
+  }
+
+  private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
+    final URI unitsUrl = new URIBuilder(UNITS_URL).addParameter("lesson", String.valueOf(lessonId)).build();
+    final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
+    if (unitContainer.units.size() != 1) {
+      LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
+      return;
+    }
+
+    final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL);
+    for (Integer step : unitContainer.units.get(0).assignments) {
+      builder.addParameter("ids[]", String.valueOf(step));
+    }
+    final URI assignmentUrl = builder.build();
+    final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
+    if (assignments.assignments.size() > 0) {
+      for (StepicWrappers.Assignment assignment : assignments.assignments) {
+        final HttpPost post = new HttpPost(STEPIC_API_URL + VIEWS_URL);
+        final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
+        post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
+        setHeaders(post, CONTENT_TYPE_APPL_JSON);
+        final CloseableHttpResponse viewPostResult = client.execute(post);
+        if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
+          LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
+        }
+      }
+    }
+    else {
+      LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
+    }
+  }
+
+  public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
+                                                   @NotNull final String user, int reaction) {
+    final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL);
+    final String json = new Gson()
+      .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
+    post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
+    final CloseableHttpClient client = getHttpClient(project);
+    setHeaders(post, CONTENT_TYPE_APPL_JSON);
+    try {
+      final CloseableHttpResponse execute = client.execute(post);
+      return execute.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED;
+    }
+    catch (IOException e) {
+      LOG.warn(e.getMessage());
+    }
+    return false;
+  }
+
+  public static void addNextRecommendedTask(@NotNull final Project project, int reaction) {
+    final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
+    final Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course != null && editor != null && editor.getTaskFile() != null) {
+      final StepicUser user = StudyTaskManager.getInstance(project).getUser();
+
+      final boolean recommendationReaction =
+        user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id),
+                                                   String.valueOf(user.id), reaction);
+      if (recommendationReaction) {
+        final Task task = getNextRecommendation(project, course);
+
+        if (task != null) {
+          final Lesson adaptive = course.getLessons().get(0);
+          final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
+          if (reaction == 0 || reaction == -1) {
+            unsolvedTask.setName(task.getName());
+            unsolvedTask.setStepicId(task.getStepicId());
+            unsolvedTask.setText(task.getText());
+            unsolvedTask.getTestsText().clear();
+            final Map<String, String> testsText = task.getTestsText();
+            for (String testName : testsText.keySet()) {
+              unsolvedTask.addTestsTexts(testName, testsText.get(testName));
+            }
+            final Map<String, TaskFile> taskFiles = task.getTaskFiles();
+            if (taskFiles.size() == 1) {
+              final TaskFile taskFile = editor.getTaskFile();
+              taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
+              ApplicationManager.getApplication().invokeLater(() ->
+                                                                ApplicationManager.getApplication().runWriteAction(() ->
+                                                                                                                     editor.getEditor()
+                                                                                                                       .getDocument()
+                                                                                                                       .setText(
+                                                                                                                         taskFiles.get(
+                                                                                                                           DEFAULT_TASKFILE_NAME).text)));
+            }
+            else {
+              LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
+            }
+
+            final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+            final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size()));
+            StudyProjectGenerator.flushTask(task, taskDirectory);
+            StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
+            final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+
+            if (lessonDir != null) {
+              createTestFiles(course, task, unsolvedTask, lessonDir);
+            }
+            final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
+            if (window != null) {
+              window.setTaskText(unsolvedTask.getText(), unsolvedTask.getTaskDir(project), project);
+            }
+          }
+          else {
+            adaptive.addTask(task);
+            task.setIndex(adaptive.getTaskList().size());
+            final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+
+            if (lessonDir != null) {
+              ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+                try {
+                  StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
+                }
+                catch (IOException e) {
+                  LOG.warn(e.getMessage());
+                }
+              }));
+            }
+
+            final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex()));
+            StudyProjectGenerator.flushLesson(lessonDirectory, adaptive);
+            StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
+            adaptive.initLesson(course, true);
+          }
+        }
+        ApplicationManager.getApplication().invokeLater(() -> {
+          VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
+          ProjectView.getInstance(project).refresh();
+        });
+      }
+      else {
+        LOG.warn("Recommendation reactions weren't posted");
+      }
+    }
+  }
+
+  private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
+    ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+      try {
+        final VirtualFile taskDir = VfsUtil
+          .findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
+        final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
+        File newResourceRoot = null;
+        if (taskDir != null) {
+          newResourceRoot = new File(resourceRoot, taskDir.getName());
+          File[] filesInTask = newResourceRoot.listFiles();
+          if (filesInTask != null) {
+            for (File file : filesInTask) {
+              String fileName = file.getName();
+              if (!task.isTaskFile(fileName)) {
+                File resourceFile = new File(newResourceRoot, fileName);
+                File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
+                FileUtil.copy(resourceFile, fileInProject);
+              }
+            }
+          }
+        }
+        else {
+          LOG.warn("Task directory is null");
+        }
+      }
+      catch (IOException e) {
+        LOG.warn(e.getMessage());
+      }
+    }));
+  }
+
+  @NotNull
+  private static Task getTaskFromStep(Project project,
+                                      int lessonID,
+                                      @NotNull final StepicWrappers.Step step, @NotNull String name) {
+    final Task task = new Task();
+    task.setName(name);
+    task.setStepicId(lessonID);
+    task.setText(step.text);
+    task.setStatus(StudyStatus.Unchecked);
+    if (step.options.samples != null) {
+      final StringBuilder builder = new StringBuilder();
+      for (List<String> sample : step.options.samples) {
+        if (sample.size() == 2) {
+          builder.append("<b>Sample Input:</b><br>");
+          builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
+          builder.append("<br>");
+          builder.append("<b>Sample Output:</b><br>");
+          builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
+          builder.append("<br><br>");
+        }
+      }
+      task.setText(task.getText() + "<br>" + builder.toString());
+    }
+
+    if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
+      String builder = "<b>Memory limit</b>: " +
+                       step.options.executionMemoryLimit + " Mb" +
+                       "<br>" +
+                       "<b>Time limit</b>: " +
+                       step.options.executionTimeLimit + "s" +
+                       "<br><br>";
+      task.setText(task.getText() + builder);
+    }
+
+    if (step.options.test != null) {
+      for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
+        task.addTestsTexts(wrapper.name, wrapper.text);
+      }
+    }
+    else {
+      if (step.options.samples != null) {
+        createTestFileFromSamples(task, step.options.samples);
+      }
+    }
+
+    task.taskFiles = new HashMap<String, TaskFile>();      // TODO: it looks like we don't need taskFiles as map anymore
+    if (step.options.files != null) {
+      for (TaskFile taskFile : step.options.files) {
+        task.taskFiles.put(taskFile.name, taskFile);
+      }
+    }
+    else {
+      final TaskFile taskFile = new TaskFile();
+      taskFile.name = "code";
+      final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
+      taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
+      task.taskFiles.put("code.py", taskFile);
+    }
+    return task;
+  }
+
+  private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
+                                               @NotNull final Task task, @NotNull final Project project) {
+
+    if (codeTemplates != null) {
+      final String languageString = getLanguageString(task, project);
+      if (languageString != null) {
+        return codeTemplates.getTemplateForLanguage(languageString);
+      }
+    }
+
+    return null;
+  }
+
+  @Nullable
+  public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
+    int attemptId = -1;
+    try {
+      attemptId = getAttemptId(project, task, ATTEMPTS_URL);
+    }
+    catch (IOException e) {
+      LOG.warn(e.getMessage());
+    }
+    if (attemptId != -1) {
+      final Editor editor = StudyUtils.getSelectedEditor(project);
+      String language = getLanguageString(task, project);
+      if (editor != null && language != null) {
+        final CloseableHttpClient client = getHttpClient(project);
+        StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
+
+        final StepicUser user = StudyTaskManager.getInstance(project).getUser();
+        if (user != null) {
+          final int id = user.getId();
+          wrapper = getCheckResults(attemptId, id, client, wrapper);
+          if (wrapper.submissions.length == 1) {
+            final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
+            return Pair.create(isSolved, wrapper.submissions[0].hint);
+          }
+          else {
+            LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
+          }
+        }
+        else {
+          LOG.warn("User is null");
+        }
+      }
+    }
+    else {
+      LOG.warn("Got an incorrect attempt id: " + attemptId);
+    }
+    return Pair.create(false, "");
+  }
+
+  @Nullable
+  private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
+                                                                            final int attemptId,
+                                                                            @NotNull final String language,
+                                                                            @NotNull final String text) {
+    final CloseableHttpResponse response;
+    try {
+      final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
+        new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, text);
+      final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL);
+      setHeaders(httpPost, CONTENT_TYPE_APPL_JSON);
+      try {
+        httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
+      }
+      catch (UnsupportedEncodingException e) {
+        LOG.warn(e.getMessage());
+      }
+      response = client.execute(httpPost);
+      return new Gson().fromJson(EntityUtils.toString(response.getEntity()), StepicWrappers.ResultSubmissionWrapper.class);
+    }
+    catch (IOException e) {
+      LOG.warn(e.getMessage());
+    }
+    return null;
+  }
+
+  @NotNull
+  private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
+                                                                        int id,
+                                                                        CloseableHttpClient client,
+                                                                        StepicWrappers.ResultSubmissionWrapper wrapper) {
+    try {
+      while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
+        TimeUnit.MILLISECONDS.sleep(500);
+        final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL)
+          .addParameter("attempt", String.valueOf(attemptId))
+          .addParameter("order", "desc")
+          .addParameter("user", String.valueOf(id))
+          .build();
+        final HttpGet httpGet = new HttpGet(submissionURI);
+        setHeaders(httpGet, CONTENT_TYPE_APPL_JSON);
+        final CloseableHttpResponse httpResponse = client.execute(httpGet);
+        final String entity = EntityUtils.toString(httpResponse.getEntity());
+        wrapper = new Gson().fromJson(entity, StepicWrappers.ResultSubmissionWrapper.class);
+      }
+    }
+    catch (InterruptedException e) {
+      LOG.warn(e.getMessage());
+    }
+    catch (IOException e) {
+      LOG.warn(e.getMessage());
+    }
+    catch (URISyntaxException e) {
+      LOG.warn(e.getMessage());
+    }
+    return wrapper;
+  }
+
+  @Nullable
+  private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
+    final Language pythonLanguage = Language.findLanguageByID("Python");
+    if (pythonLanguage != null) {
+      final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
+      if (language != null) {
+        final String versionString = language.getVersionString();
+        if (versionString != null) {
+          final List<String> versionStringParts = StringUtil.split(versionString, " ");
+          if (versionStringParts.size() == 2) {
+            return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3;
+          }
+        }
+      }
+      else {
+        StudyUtils.showNoSdkNotification(task, project);
+      }
+    }
+    return null;
+  }
+
+  private static int getAttemptId(@NotNull final Project project, @NotNull Task task, @NotNull final String attempts) throws IOException {
+    final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
+
+    final HttpPost post = new HttpPost(STEPIC_API_URL + attempts);
+    post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
+
+    final CloseableHttpClient client = getHttpClient(project);
+    setHeaders(post, CONTENT_TYPE_APPL_JSON);
+    final CloseableHttpResponse httpResponse = client.execute(post);
+    final String entity = EntityUtils.toString(httpResponse.getEntity());
+    final StepicWrappers.AttemptContainer container =
+      new Gson().fromJson(entity, StepicWrappers.AttemptContainer.class);
+    return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
+  }
+
+  private static void createTestFileFromSamples(@NotNull final Task task,
+                                                @NotNull final List<List<String>> samples) {
+
+    String testText = "from test_helper import check_samples\n\n" +
+                      "if __name__ == '__main__':\n" +
+                      "    check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
+    task.addTestsTexts("tests.py", testText);
+  }
+}
index 8a20545cfc2ed02c9a2b6522c6ff54bc60c9cc13..f796dddbaad9e1cf0445bdd266b8e86431f913bf 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,9 +52,9 @@ import java.security.cert.X509Certificate;
 import java.util.*;
 
 public class EduStepicConnector {
+  private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
   private static final String stepicUrl = "https://stepic.org/";
   private static final String stepicApiUrl = stepicUrl + "api/";
-  private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
   private static String ourCSRFToken = "";
   private static CloseableHttpClient ourClient;
 
@@ -70,15 +65,21 @@ public class EduStepicConnector {
   private EduStepicConnector() {
   }
 
-  public static boolean login(@NotNull final String user, @NotNull final String password) {
+  public static StepicUser login(@NotNull final String username, @NotNull final String password) {
     initializeClient();
-    return postCredentials(user, password);
+    if (postCredentials(username, password)) {
+      final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
+      if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
+        return stepicUserWrapper.users.get(0);
+      }
+    }
+    return null;
   }
 
   @Nullable
-  public static AuthorWrapper getCurrentUser() {
+  public static StepicWrappers.AuthorWrapper getCurrentUser() {
     try {
-      return getFromStepic("stepics/1", AuthorWrapper.class);
+      return getFromStepic("stepics/1", StepicWrappers.AuthorWrapper.class);
     }
     catch (IOException e) {
       LOG.warn("Couldn't get author info");
@@ -90,7 +91,7 @@ public class EduStepicConnector {
     final HttpPost userRequest = new HttpPost(stepicApiUrl + "users");
     initializeClient();
     setHeaders(userRequest, "application/json");
-    String requestBody = new Gson().toJson(new UserWrapper(user, password));
+    String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
     userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
 
     try {
@@ -109,38 +110,47 @@ public class EduStepicConnector {
     return true;
   }
 
-  private static void initializeClient() {
-    final HttpGet request = new HttpGet(stepicUrl);
-    request.addHeader(new BasicHeader("referer", "https://stepic.org"));
-    request.addHeader(new BasicHeader("content-type", "application/json"));
+  public static void initializeClient() {
+    if (ourClient == null) {
+      final HttpGet request = new HttpGet(stepicUrl);
+      request.addHeader(new BasicHeader("referer", "https://stepic.org"));
+      request.addHeader(new BasicHeader("content-type", "application/json"));
 
 
-    HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
-      setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
-    ourCookieStore = new BasicCookieStore();
+      HttpClientBuilder builder =
+        HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
+          setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
+      ourCookieStore = new BasicCookieStore();
 
-    try {
-      // Create a trust manager that does not validate certificate for this connection
-      TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
-        public X509Certificate[] getAcceptedIssuers() { return null; }
-        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
-        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
-      }};
-      SSLContext sslContext = SSLContext.getInstance("TLS");
-      sslContext.init(null, trustAllCerts, new SecureRandom());
-      ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
-
-      ourClient.execute(request);
-      saveCSRFToken();
-    }
-    catch (IOException e) {
-      LOG.error(e.getMessage());
-    }
-    catch (NoSuchAlgorithmException e) {
-      LOG.error(e.getMessage());
-    }
-    catch (KeyManagementException e) {
-      LOG.error(e.getMessage());
+      try {
+        // Create a trust manager that does not validate certificate for this connection
+        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+          public X509Certificate[] getAcceptedIssuers() {
+            return null;
+          }
+
+          public void checkClientTrusted(X509Certificate[] certs, String authType) {
+          }
+
+          public void checkServerTrusted(X509Certificate[] certs, String authType) {
+          }
+        }};
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(null, trustAllCerts, new SecureRandom());
+        ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
+
+        ourClient.execute(request);
+        saveCSRFToken();
+      }
+      catch (IOException e) {
+        LOG.error(e.getMessage());
+      }
+      catch (NoSuchAlgorithmException e) {
+        LOG.error(e.getMessage());
+      }
+      catch (KeyManagementException e) {
+        LOG.error(e.getMessage());
+      }
     }
   }
 
@@ -164,14 +174,7 @@ public class EduStepicConnector {
     nvps.add(new BasicNameValuePair("password", password));
     nvps.add(new BasicNameValuePair("remember", "on"));
 
-    try {
-      request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
-    }
-    catch (UnsupportedEncodingException e) {
-      LOG.error(e.getMessage());
-      ourClient = null;
-      return false;
-    }
+    request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
 
     setHeaders(request, "application/x-www-form-urlencoded");
 
@@ -182,7 +185,8 @@ public class EduStepicConnector {
       if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
         final HttpEntity responseEntity = response.getEntity();
         final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
-        LOG.error("Failed to login " + responseString);
+        LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
+        LOG.debug("Failed to login " + responseString);
         ourClient = null;
         return false;
       }
@@ -195,7 +199,7 @@ public class EduStepicConnector {
     return true;
   }
 
-  private static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
+  static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
     final HttpGet request = new HttpGet(stepicApiUrl + link);
     if (ourClient == null) {
       initializeClient();
@@ -212,6 +216,15 @@ public class EduStepicConnector {
     Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
     return gson.fromJson(responseString, container);
   }
+  
+  @NotNull
+  public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
+    if (ourClient == null) {
+      login(project);
+      initializeClient();
+    }
+    return ourClient;
+  }
 
   @NotNull
   public static List<CourseInfo> getCourses() {
@@ -226,21 +239,21 @@ public class EduStepicConnector {
     catch (IOException e) {
       LOG.error("Cannot load course list " + e.getMessage());
     }
-    return Collections.emptyList();
+    return Collections.singletonList(CourseInfo.INVALID_COURSE);
   }
 
   private static boolean addCoursesFromStepic(List<CourseInfo> result, int pageNumber) throws IOException {
     final String url = pageNumber == 0 ? "courses" : "courses?page=" + String.valueOf(pageNumber);
-    final CoursesContainer coursesContainer = getFromStepic(url, CoursesContainer.class);
+    final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
     final List<CourseInfo> courseInfos = coursesContainer.courses;
     for (CourseInfo info : courseInfos) {
       final String courseType = info.getType();
-      if (StringUtil.isEmptyOrSpaces(courseType)) continue;
+      if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue;
       final List<String> typeLanguage = StringUtil.split(courseType, " ");
-      if (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0))) {
-
+      // TODO: should adaptive course be of PyCharmType ?
+      if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
         for (Integer instructor : info.instructors) {
-          final CourseInfo.Author author = getFromStepic("users/" + String.valueOf(instructor), AuthorWrapper.class).users.get(0);
+          final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0);
           info.addAuthor(author);
         }
 
@@ -250,34 +263,55 @@ public class EduStepicConnector {
     return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
   }
 
-  public static Course getCourse(@NotNull final CourseInfo info) {
+  public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
     final Course course = new Course();
     course.setAuthors(info.getAuthors());
     course.setDescription(info.getDescription());
-    course.setName(info.getName());
-    String courseType = info.getType();
-    course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
+    course.setAdaptive(info.isAdaptive());
+    course.setId(info.id);
     course.setUpToDate(true);  // TODO: get from stepic
-    try {
-      for (Integer section : info.sections) {
-        course.addLessons(getLessons(section));
+    
+    if (!course.isAdaptive()) {
+      String courseType = info.getType();
+      course.setName(info.getName());
+      course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
+      try {
+        for (Integer section : info.sections) {
+          course.addLessons(getLessons(section));
+        }
+        return course;
+      }
+      catch (IOException e) {
+        LOG.error("IOException " + e.getMessage());
       }
-      return course;
     }
-    catch (IOException e) {
-      LOG.error("IOException " + e.getMessage());
+    else {
+      final Lesson lesson = new Lesson();
+      course.setName(info.getName());
+      //TODO: more specific name?
+      lesson.setName("Adaptive");
+      course.addLesson(lesson);
+      final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
+      if (recommendation != null) {
+        lesson.addTask(recommendation);
+      }
+
+      return course;
     }
     return null;
   }
 
   public static List<Lesson> getLessons(int sectionId) throws IOException {
-    final SectionContainer sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), SectionContainer.class);
+    final StepicWrappers.SectionContainer
+      sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
     List<Integer> unitIds = sectionContainer.sections.get(0).units;
     final List<Lesson> lessons = new ArrayList<Lesson>();
     for (Integer unitId : unitIds) {
-      UnitContainer unit = getFromStepic("units/" + String.valueOf(unitId), UnitContainer.class);
+      StepicWrappers.UnitContainer
+        unit = getFromStepic("units/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
       int lessonID = unit.units.get(0).lesson;
-      LessonContainer lesson = getFromStepic("lessons/" + String.valueOf(lessonID), LessonContainer.class);
+      StepicWrappers.LessonContainer
+        lesson = getFromStepic("lessons/" + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
       Lesson realLesson = lesson.lessons.get(0);
       realLesson.taskList = new ArrayList<Task>();
       for (Integer s : realLesson.steps) {
@@ -291,13 +325,13 @@ public class EduStepicConnector {
   }
 
   private static void createTask(Lesson lesson, Integer stepicId) throws IOException {
-    final Step step = getStep(stepicId);
+    final StepicWrappers.Step step = getStep(stepicId);
     if (!step.name.equals(PYCHARM_PREFIX)) return;
     final Task task = new Task();
     task.setStepicId(stepicId);
     task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX);
     task.setText(step.text);
-    for (TestFileWrapper wrapper : step.options.test) {
+    for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
       task.addTestsTexts(wrapper.name, wrapper.text);
     }
 
@@ -310,8 +344,8 @@ public class EduStepicConnector {
     lesson.taskList.add(task);
   }
 
-  public static Step getStep(Integer step) throws IOException {
-    return getFromStepic("steps/" + String.valueOf(step), StepContainer.class).steps.get(0).block;
+  public static StepicWrappers.Step getStep(Integer step) throws IOException {
+    return getFromStepic("steps/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block;
   }
 
 
@@ -334,14 +368,13 @@ public class EduStepicConnector {
         return;
       }
       else {
-        final boolean success = login(login, password);
-        if (!success) return;
+        if (login(login, password) == null) return;
       }
     }
 
     final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts");
     setHeaders(attemptRequest, "application/json");
-    String attemptRequestBody = new Gson().toJson(new AttemptWrapper(task.getStepicId()));
+    String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
     attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
 
     try {
@@ -352,12 +385,12 @@ public class EduStepicConnector {
       if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
         LOG.error("Failed to make attempt " + attemptResponseString);
       }
-      final AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, AttemptContainer.class).attempts.get(0);
+      final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0);
 
       final Map<String, TaskFile> taskFiles = task.getTaskFiles();
-      final ArrayList<SolutionFile> files = new ArrayList<SolutionFile>();
+      final ArrayList<StepicWrappers.SolutionFile> files = new ArrayList<StepicWrappers.SolutionFile>();
       for (TaskFile fileEntry : taskFiles.values()) {
-        files.add(new SolutionFile(fileEntry.name, fileEntry.text));
+        files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
       }
       postSubmission(passed, attempt, files);
     }
@@ -366,11 +399,11 @@ public class EduStepicConnector {
     }
   }
 
-  private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList<SolutionFile> files) throws IOException {
+  private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
     final HttpPost request = new HttpPost(stepicApiUrl + "submissions");
     setHeaders(request, "application/json");
 
-    String requestBody = new Gson().toJson(new SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
+    String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
     final CloseableHttpResponse response = ourClient.execute(request);
     final HttpEntity responseEntity = response.getEntity();
@@ -398,15 +431,15 @@ public class EduStepicConnector {
     indicator.setText("Uploading course to " + stepicUrl);
     final HttpPost request = new HttpPost(stepicApiUrl + "courses");
     if (ourClient == null || !relogin) {
-      if (!login()) return;
+      if (!login(project)) return;
     }
-    final AuthorWrapper user = getCurrentUser();
+    final StepicWrappers.AuthorWrapper user = getCurrentUser();
     if (user != null) {
       course.setAuthors(user.users);
     }
 
     setHeaders(request, "application/json");
-    String requestBody = new Gson().toJson(new CourseWrapper(course));
+    String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
 
     try {
@@ -416,13 +449,13 @@ public class EduStepicConnector {
       final StatusLine line = response.getStatusLine();
       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
         if (!relogin) {
-          login();
+          login(project);
           postCourse(project, course, true, indicator);
         }
         LOG.error("Failed to push " + responseString);
         return;
       }
-      final CourseInfo postedCourse = new Gson().fromJson(responseString, CoursesContainer.class).courses.get(0);
+      final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
 
       final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
       int position = 1;
@@ -439,14 +472,13 @@ public class EduStepicConnector {
     }
   }
 
-  private static boolean login() {
-    final String login = StudySettings.getInstance().getLogin();
+  private static boolean login(@NotNull final Project project) {
+    final String login = StudyTaskManager.getInstance(project).getLogin();
     if (StringUtil.isEmptyOrSpaces(login)) {
       return showLoginDialog();
     }
     else {
-      boolean success = login(login, StudySettings.getInstance().getPassword());
-      if (!success) {
+      if (login(login, StudyTaskManager.getInstance(project).getPassword()) == null) {
         return showLoginDialog();
       }
     }
@@ -498,8 +530,8 @@ public class EduStepicConnector {
   private static void postUnit(int lessonId, int position, int sectionId) {
     final HttpPost request = new HttpPost(stepicApiUrl + "units");
     setHeaders(request, "application/json");
-    final UnitWrapper unitWrapper = new UnitWrapper();
-    unitWrapper.unit = new Unit();
+    final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
+    unitWrapper.unit = new StepicWrappers.Unit();
     unitWrapper.unit.lesson = lessonId;
     unitWrapper.unit.position = position;
     unitWrapper.unit.section = sectionId;
@@ -524,11 +556,11 @@ public class EduStepicConnector {
   private static int postModule(int courseId, int position, @NotNull final String title) {
     final HttpPost request = new HttpPost(stepicApiUrl + "sections");
     setHeaders(request, "application/json");
-    final Section section = new Section();
+    final StepicWrappers.Section section = new StepicWrappers.Section();
     section.course = courseId;
     section.title = title;
     section.position = position;
-    final SectionWrapper sectionContainer = new SectionWrapper();
+    final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
     sectionContainer.section = section;
     String requestBody = new Gson().toJson(sectionContainer);
     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
@@ -541,7 +573,8 @@ public class EduStepicConnector {
       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
         LOG.error("Failed to push " + responseString);
       }
-      final Section postedSection = new Gson().fromJson(responseString, SectionContainer.class).sections.get(0);
+      final StepicWrappers.Section
+        postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
       return postedSection.id;
     }
     catch (IOException e) {
@@ -550,17 +583,17 @@ public class EduStepicConnector {
     return -1;
   }
 
-  public static int updateLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
+  public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
     final HttpPut request = new HttpPut(stepicApiUrl + "lessons/" + String.valueOf(lesson.id));
     if (ourClient == null) {
-      if (!login()) {
+      if (!login(project)) {
         LOG.error("Failed to push lesson");
         return 0;
       }
     }
 
     setHeaders(request, "application/json");
-    String requestBody = new Gson().toJson(new LessonWrapper(lesson));
+    String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
 
     try {
@@ -589,14 +622,14 @@ public class EduStepicConnector {
     return -1;
   }
 
-  public static int postLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
+  public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
     final HttpPost request = new HttpPost(stepicApiUrl + "lessons");
     if (ourClient == null) {
-      login();
+      login(project);
     }
 
     setHeaders(request, "application/json");
-    String requestBody = new Gson().toJson(new LessonWrapper(lesson));
+    String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
 
     try {
@@ -646,7 +679,7 @@ public class EduStepicConnector {
     setHeaders(request, "application/json");
     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
     ApplicationManager.getApplication().invokeLater(() -> {
-      final String requestBody = gson.toJson(new StepSourceWrapper(project, task, lessonId));
+      final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
       request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
 
       try {
@@ -664,274 +697,9 @@ public class EduStepicConnector {
     });
   }
 
-  private static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
+  static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
     request.addHeader(new BasicHeader("referer", stepicUrl));
     request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
     request.addHeader(new BasicHeader("content-type", contentType));
   }
-
-  private static class StepContainer {
-    List<StepSource> steps;
-  }
-
-  private static class Step {
-    @Expose StepOptions options;
-    @Expose String text;
-    @Expose String name = "pycharm";
-    @Expose StepOptions source;
-
-    public static Step fromTask(Project project, @NotNull final Task task) {
-      final Step step = new Step();
-      step.text = task.getTaskText(project);
-      step.source = StepOptions.fromTask(project, task);
-      return step;
-    }
-  }
-
-  private static class StepOptions {
-    @Expose List<TestFileWrapper> test;
-    @Expose String title;
-    @Expose List<TaskFile> files;
-    @Expose String text;
-
-    public static StepOptions fromTask(final Project project, @NotNull final Task task) {
-      final StepOptions source = new StepOptions();
-      setTests(task, source, project);
-      source.files = new ArrayList<TaskFile>();
-      source.title = task.getName();
-      for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-        final TaskFile taskFile = new TaskFile();
-        TaskFile.copy(entry.getValue(), taskFile);
-        ApplicationManager.getApplication().runWriteAction(() -> {
-          final VirtualFile taskDir = task.getTaskDir(project);
-          assert taskDir != null;
-          VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-          assert ideaDir != null;
-          EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile);
-        });
-        taskFile.name = entry.getKey();
-
-        VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-        if (ideaDir == null) return null;
-        final VirtualFile file = ideaDir.findChild(taskFile.name);
-        try {
-          if (file != null) {
-            if (EduUtils.isImage(taskFile.name)) {
-              taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()));
-            }
-            else {
-              taskFile.text = FileUtil.loadTextAndClose(file.getInputStream());
-            }
-          }
-        }
-        catch (IOException e) {
-          LOG.error("Can't find file " + file.getPath());
-        }
-
-        source.files.add(taskFile);
-      }
-      return source;
-    }
-
-    private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) {
-      final Map<String, String> testsText = task.getTestsText();
-      if (testsText.isEmpty()) {
-        ApplicationManager.getApplication().runReadAction(() -> {
-          source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project)));
-        });
-      }
-      else {
-        source.test = new ArrayList<TestFileWrapper>();
-        for (Map.Entry<String, String> entry : testsText.entrySet()) {
-          source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue()));
-        }
-      }
-    }
-  }
-
-  private static class CoursesContainer {
-    public List<CourseInfo> courses;
-    public Map meta;
-  }
-
-  static class StepSourceWrapper {
-    @Expose
-    StepSource stepSource;
-
-    public StepSourceWrapper(Project project, Task task, int lessonId) {
-      stepSource = new StepSource(project, task, lessonId);
-    }
-  }
-
-  static class CourseWrapper {
-    CourseInfo course;
-
-    public CourseWrapper(Course course) {
-      this.course = new CourseInfo();
-      this.course.setName(course.getName());
-      this.course.setDescription(course.getDescription());
-      this.course.setAuthors(course.getAuthors());
-    }
-  }
-
-  static class LessonWrapper {
-    Lesson lesson;
-
-    public LessonWrapper(Lesson lesson) {
-      this.lesson = new Lesson();
-      this.lesson.setName(lesson.getName());
-      this.lesson.id = lesson.id;
-      this.lesson.steps = new ArrayList<Integer>();
-    }
-  }
-
-  static class LessonContainer {
-    List<Lesson> lessons;
-  }
-
-  static class StepSource {
-    @Expose Step block;
-    @Expose int position = 0;
-    @Expose int lesson = 0;
-
-    public StepSource(Project project, Task task, int lesson) {
-      this.lesson = lesson;
-      position = task.getIndex();
-      block = Step.fromTask(project, task);
-    }
-  }
-
-  static class TestFileWrapper {
-    @Expose private final String name;
-    @Expose private final String text;
-
-    public TestFileWrapper(String name, String text) {
-      this.name = name;
-      this.text = text;
-    }
-  }
-
-  static class Section {
-    List<Integer> units;
-    int course;
-    String title;
-    int position;
-    int id;
-  }
-
-  static class SectionWrapper {
-    Section section;
-  }
-
-  static class SectionContainer {
-    List<Section> sections;
-    List<Lesson> lessons;
-
-    List<Unit> units;
-  }
-
-  static class Unit {
-    int id;
-    int section;
-    int lesson;
-    int position;
-  }
-
-  static class UnitContainer {
-
-    List<Unit> units;
-  }
-
-  static class UnitWrapper{
-    Unit unit;
-  }
-
-
-  static class AttemptWrapper {
-    static class Attempt {
-      public Attempt(int step) {
-        this.step = step;
-      }
-
-      int step;
-      int id;
-    }
-    public AttemptWrapper(int step) {
-      attempt = new Attempt(step);
-    }
-
-    Attempt attempt;
-  }
-
-  static class AttemptContainer {
-    List<AttemptWrapper.Attempt> attempts;
-  }
-
-  static class SolutionFile {
-    String name;
-    String text;
-
-    public SolutionFile(String name, String text) {
-      this.name = name;
-      this.text = text;
-    }
-  }
-
-  static class AuthorWrapper {
-    List<CourseInfo.Author> users;
-  }
-
-  static class SubmissionWrapper {
-    Submission submission;
-
-
-    public SubmissionWrapper(int attempt, String score, ArrayList<SolutionFile> files) {
-      submission = new Submission(score, attempt, files);
-    }
-
-    static class Submission {
-      int attempt;
-
-      private final Reply reply;
-
-      public Submission(String score, int attempt, ArrayList<SolutionFile> files) {
-        reply = new Reply(files, score);
-        this.attempt = attempt;
-      }
-
-      static class Reply {
-        String score;
-        List<SolutionFile> solution;
-
-        public Reply(ArrayList<SolutionFile> files, String score) {
-          this.score = score;
-          solution = files;
-        }
-      }
-    }
-
-  }
-
-  static class User {
-    String first_name;
-    String last_name;
-    String email;
-    String password;
-
-    public User(String user, String password) {
-      email = user;
-      this.password = password;
-      this.first_name = user;
-      this.last_name = user;
-    }
-  }
-
-  static class UserWrapper {
-    User user;
-
-    public UserWrapper(String user, String password) {
-      this.user = new User(user, password);
-    }
-  }
-
 }
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");
     }
   }
 
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java
new file mode 100644 (file)
index 0000000..97521b1
--- /dev/null
@@ -0,0 +1,196 @@
+package com.jetbrains.edu.learning.stepic;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
+import com.intellij.openapi.fileEditor.FileEditorManagerListener;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.progress.util.ProgressIndicatorBase;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+
+public class StepicAdaptiveReactionsPanel extends JPanel {
+  private final ReactionButtonPanel myHardPanel;
+  private final ReactionButtonPanel myBoringPanel;
+  private final Project myProject;
+  private static final int TOO_HARD_REACTION = 0;
+  private static final int TOO_BORING_REACTION = -1;
+  private static final String HARD_REACTION = "Too Hard";
+  private static final String BORING_REACTION = "Too Boring";
+  private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved";
+  private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task";
+  private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task";
+
+  public StepicAdaptiveReactionsPanel(@NotNull final Project project) {
+    myProject = project;
+    setLayout(new GridBagLayout());
+    setBackground(UIUtil.getTextFieldBackground());
+
+    myHardPanel = new ReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_HARD_REACTION);
+    myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_BORING_REACTION);
+    addFileListener();
+
+    final GridBagConstraints c = new GridBagConstraints();
+    c.fill = GridBagConstraints.HORIZONTAL;
+    c.gridx = 0;
+    c.gridy = 0;
+    add(Box.createVerticalStrut(3), c);
+    c.gridx = 1;
+    c.gridy = 1;
+    add(Box.createHorizontalStrut(3), c);
+    c.weightx = 1;
+    c.gridx = 2;
+    add(myHardPanel, c);
+    c.gridx = 3;
+    c.weightx = 0;
+    add(Box.createHorizontalStrut(3), c);
+    c.weightx = 1;
+    c.gridx = 4;
+    add(myBoringPanel, c);
+    c.gridx = 5;
+    c.weightx = 0;
+    add(Box.createHorizontalStrut(3), c);
+  }
+
+  public void setEnabledRecursive(final boolean isEnabled) {
+    ApplicationManager.getApplication().invokeLater(() -> {
+      super.setEnabled(isEnabled);
+      myHardPanel.setEnabledRecursive(isEnabled);
+      myBoringPanel.setEnabledRecursive(isEnabled);
+    });
+  }
+
+  private void addFileListener() {
+    final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() {
+      @Override
+      public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
+        final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+        final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+        StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
+      }
+
+      @Override
+      public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
+
+      }
+
+      @Override
+      public void selectionChanged(@NotNull FileEditorManagerEvent event) {
+        final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+        final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+        StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled);
+      }
+    };
+    myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener);
+  }
+
+  private class ReactionButtonPanel extends JPanel {
+    private final JPanel myButtonPanel;
+    private final JLabel myLabel;
+
+    public ReactionButtonPanel(@NotNull final String text,
+                               @NotNull final String enabledTooltip,
+                               @NotNull final String disabledTooltip,
+                               int reaction) {
+      com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject);
+      final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved;
+
+      myLabel = new JLabel(text);
+
+      myButtonPanel = new JPanel();
+      myButtonPanel.setLayout(new BoxLayout(myButtonPanel, BoxLayout.PAGE_AXIS));
+      myButtonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip);
+      myButtonPanel.add(Box.createVerticalStrut(5));
+      myButtonPanel.add(myLabel);
+      myButtonPanel.add(Box.createVerticalStrut(5));
+
+      setEnabledRecursive(isEnabled);
+
+      setLayout(new GridBagLayout());
+      setBorder(BorderFactory.createEtchedBorder());
+      add(myButtonPanel);
+      addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, reaction));
+    }
+
+    private void addMouseListener(@NotNull Runnable onClickAction) {
+      final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(onClickAction, this);
+      this.addMouseListener(mouseAdapter);
+      myButtonPanel.addMouseListener(mouseAdapter);
+      myLabel.addMouseListener(mouseAdapter);
+    }
+
+
+    public void setEnabledRecursive(final boolean isEnabled) {
+      ApplicationManager.getApplication().invokeLater(() -> {
+        super.setEnabled(isEnabled);
+        myButtonPanel.setEnabled(isEnabled);
+        myLabel.setEnabled(isEnabled);
+      });
+    }
+
+    private class ReactionMouseAdapter extends MouseAdapter {
+      private final Runnable myClickAction;
+      private final ReactionButtonPanel myPanel;
+
+      public ReactionMouseAdapter(@NotNull final Runnable onClickAction, @NotNull final ReactionButtonPanel mainPanel) {
+        myClickAction = onClickAction;
+        myPanel = mainPanel;
+      }
+
+      @Override
+      public void mouseClicked(MouseEvent e) {
+        if (e.getClickCount() == 1) {
+          final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
+          if (task != null && task.getStatus() != StudyStatus.Solved) {
+            final ProgressIndicatorBase progress = new ProgressIndicatorBase();
+            progress.setText("Loading Next Recommendation");
+            ProgressManager.getInstance().run(new Task.Backgroundable(myProject,
+                                                                      "Loading Next Recommendation") {
+              @Override
+              public void run(@NotNull ProgressIndicator indicator) {
+                StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false);
+                myClickAction.run();
+              }
+
+              @Override
+              public void onFinished() {
+                StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true);
+              }
+            });
+          }
+        }
+      }
+
+      @Override
+      public void mouseEntered(MouseEvent e) {
+        final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject);
+        if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) {
+          setBackground(UIUtil.getButtonSelectColor());
+        }
+      }
+
+      @Override
+      public void mouseExited(MouseEvent e) {
+        setBackground(UIUtil.getLabelBackground());
+      }
+
+      private void setBackground(Color color) {
+        myPanel.setBackground(color);
+        myButtonPanel.setBackground(color);
+      }
+    }
+  }
+}
+
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 065ada09436021b55a3ac15dc5a135daa2384256..8f4475372b8515239d9fab01df651aef0caf4afc 100644 (file)
@@ -3,8 +3,13 @@ package com.jetbrains.edu.learning.ui;
 import com.intellij.facet.ui.FacetValidatorsManager;
 import com.intellij.facet.ui.ValidationResult;
 import com.intellij.icons.AllIcons;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileChooser.FileChooser;
 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.DefaultProjectFactory;
+import com.intellij.openapi.project.DefaultProjectFactoryImpl;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.ui.DialogWrapper;
@@ -14,18 +19,20 @@ import com.intellij.openapi.ui.popup.PopupStep;
 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.AncestorListenerAdapter;
 import com.intellij.util.Consumer;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
 import com.jetbrains.edu.learning.stepic.CourseInfo;
 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
-import com.jetbrains.edu.learning.stepic.StudySettings;
+import com.jetbrains.edu.learning.stepic.StepicUser;
 import icons.InteractiveLearningIcons;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
+import javax.swing.event.AncestorEvent;
 import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -36,10 +43,11 @@ import java.util.List;
  * author: liana
  * data: 7/31/14.
  */
-public class StudyNewProjectPanel{
+public class StudyNewProjectPanel {
+  private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class);
   private List<CourseInfo> myAvailableCourses = new ArrayList<CourseInfo>();
   private JButton myBrowseButton;
-  private JComboBox myCoursesComboBox;
+  private JComboBox<CourseInfo> myCoursesComboBox;
   private JButton myRefreshButton;
   private JPanel myContentPanel;
   private JLabel myAuthorLabel;
@@ -50,19 +58,37 @@ public class StudyNewProjectPanel{
   private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
   private static final String INVALID_COURSE = "Selected course is invalid";
   private FacetValidatorsManager myValidationManager;
+  private boolean isComboboxInitialized;
 
-  public StudyNewProjectPanel(StudyProjectGenerator generator) {
+  public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) {
     myGenerator = generator;
-    myAvailableCourses = myGenerator.getCourses(false);
     myBrowseButton.setPreferredSize(new Dimension(28, 28));
     myRefreshButton.setPreferredSize(new Dimension(28, 28));
-    if (myAvailableCourses.isEmpty()) {
+    initListeners();
+    myRefreshButton.setVisible(true);
+    myRefreshButton.putClientProperty("JButton.buttonType", "null");
+    myRefreshButton.setIcon(AllIcons.Actions.Refresh);
+
+    myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
+    myContentPanel.addAncestorListener(new AncestorListenerAdapter() {
+      @Override
+      public void ancestorMoved(AncestorEvent event) {
+        if (!isComboboxInitialized && myContentPanel.isVisible()) {
+          isComboboxInitialized = true;
+          initCoursesCombobox();
+        }
+      }
+    });
+  }
+
+  private void initCoursesCombobox() {
+    myAvailableCourses =
+      myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject());
+    if (myAvailableCourses == null || myAvailableCourses.isEmpty()) {
       setError(CONNECTION_ERROR);
     }
     else {
-      for (CourseInfo courseInfo : myAvailableCourses) {
-        myCoursesComboBox.addItem(courseInfo);
-      }
+      addCoursesToCombobox(myAvailableCourses);
       final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
       final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
       myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
@@ -72,13 +98,6 @@ public class StudyNewProjectPanel{
       myGenerator.setSelectedCourse(selectedCourse);
       setOK();
     }
-    initListeners();
-    myRefreshButton.setVisible(true);
-    myRefreshButton.putClientProperty("JButton.buttonType", "null");
-    myRefreshButton.setIcon(AllIcons.Actions.Refresh);
-
-    myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
-
   }
 
   private void setupBrowseButton() {
@@ -177,21 +196,22 @@ public class StudyNewProjectPanel{
   private class RefreshActionListener implements ActionListener {
     @Override
     public void actionPerformed(ActionEvent e) {
-      refreshCoursesList();
+      final List<CourseInfo> courses =
+        myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject());
+      if (courses != null) {
+        refreshCoursesList(courses);
+      }
     }
   }
 
-  private void refreshCoursesList() {
-    final List<CourseInfo> courses = myGenerator.getCourses(true);
+  private void refreshCoursesList(@NotNull final List<CourseInfo> courses) {
     if (courses.isEmpty()) {
       setError(CONNECTION_ERROR);
       return;
     }
     myCoursesComboBox.removeAllItems();
 
-    for (CourseInfo courseInfo : courses) {
-      myCoursesComboBox.addItem(courseInfo);
-    }
+    addCoursesToCombobox(courses);
     myGenerator.setSelectedCourse(StudyUtils.getFirst(courses));
 
     myGenerator.setCourses(courses);
@@ -199,6 +219,20 @@ public class StudyNewProjectPanel{
     StudyProjectGenerator.flushCache(myAvailableCourses);
   }
 
+  private void addCoursesToCombobox(@NotNull List<CourseInfo> courses) {
+    for (CourseInfo courseInfo : courses) {
+      if (!courseInfo.isAdaptive()) {
+        myCoursesComboBox.addItem(courseInfo);
+      }
+      else {
+        final boolean isLoggedIn = myGenerator.myUser != null;
+        if (isLoggedIn) {
+          myCoursesComboBox.addItem(courseInfo);
+        }
+      }
+    }
+  }
+
 
   /**
    * Handles selecting course in combo box
@@ -213,10 +247,11 @@ public class StudyNewProjectPanel{
       if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
         myAuthorLabel.setText("");
         myDescriptionLabel.setText("");
+        setError(INVALID_COURSE);
         return;
       }
       final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
-      myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ?"Author: " + authorsString : "");
+      myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
       myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
       myDescriptionLabel.setText(selectedCourse.getDescription());
       myGenerator.setSelectedCourse(selectedCourse);
@@ -238,6 +273,7 @@ public class StudyNewProjectPanel{
 
     protected AddRemoteDialog() {
       super(null);
+      setTitle("Login To Stepic");
       myRemoteCourse = new StudyAddRemoteCourse();
       init();
     }
@@ -248,8 +284,15 @@ public class StudyNewProjectPanel{
       return myRemoteCourse.getContentPanel();
     }
 
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myRemoteCourse.getLoginField();
+    }
+
     @Override
     protected void doOKAction() {
+      super.doOKAction();
       if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
         myRemoteCourse.setError("Please, enter your login");
         return;
@@ -259,15 +302,26 @@ public class StudyNewProjectPanel{
         return;
       }
 
-      super.doOKAction();
-      final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
-      if (!isSuccess) {
-        setError("Failed to log in");
-      }
-      StudySettings.getInstance().setLogin(myRemoteCourse.getLogin());
-      StudySettings.getInstance().setPassword(myRemoteCourse.getPassword());
-      refreshCoursesList();
+      ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
+        ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
+
+        final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(),
+                                                                                               myRemoteCourse.getPassword()));
+        if (stepicUser != null) {
+          stepicUser.setEmail(myRemoteCourse.getLogin());
+          stepicUser.setPassword(myRemoteCourse.getPassword());
+          myGenerator.myUser = stepicUser;
+
+
+          final List<CourseInfo> courses = myGenerator.getCoursesAsynchronouslyIfNeeded(true);
+          if (courses != null) {
+            ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses));
+          }
+        }
+        else {
+          setError("Failed to login");
+        }
+      }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
     }
   }
-
 }
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..652d758
--- /dev/null
@@ -0,0 +1,55 @@
+package com.jetbrains.edu.learning.ui
+
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.fileEditor.FileEditorManagerEvent
+import com.intellij.openapi.fileEditor.FileEditorManagerListener
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.wm.ToolWindow
+import com.jetbrains.edu.learning.StudyUtils
+import com.jetbrains.python.console.PythonConsoleView
+
+
+class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() {  
+  override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
+    val currentTask = StudyUtils.getCurrentTask(project)
+    if (currentTask != null) {
+      val sdk = StudyUtils.findSdk(currentTask, project)
+      if (sdk != null) {
+        val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk);
+        testResultsToolWindow.isEditable = false
+        testResultsToolWindow.isConsoleEditorEnabled = false
+        testResultsToolWindow.prompt = null
+        toolWindow.isToHideOnEmptyContent = true
+
+        val contentManager = toolWindow.contentManager
+        val content = contentManager.factory.createContent(testResultsToolWindow.component, null, false)
+        contentManager.addContent(content)
+
+        project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow))
+      }
+      else {
+        StudyUtils.showNoSdkNotification(currentTask, project)
+      }
+    }
+  }
+
+  fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener {
+
+    return object : FileEditorManagerListener {
+
+      override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
+      }
+
+      override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
+        toolWindow.setAvailable(false, {})
+      }
+
+      override fun selectionChanged(event: FileEditorManagerEvent) {
+        toolWindow.setAvailable(false, {})
+      }
+    }
+  }
+}
+
+
index 846b93633f8749ab5fd84e061b0350d261b3cb17..d47ef138d30884dbbcb0cbd8c6cd8ef19f7e621e 100644 (file)
@@ -35,6 +35,7 @@ import com.intellij.util.ui.JBUI;
 import com.jetbrains.edu.learning.*;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
+import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel;
 import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
@@ -45,6 +46,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
   private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
   private static final String TASK_INFO_ID = "taskInfo";
   private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
+
   private final JBCardLayout myCardLayout;
   private final JPanel myContentPanel;
   private final OnePixelSplitter mySplitPane;
@@ -63,7 +65,14 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
     JPanel toolbarPanel = createToolbarPanel(getActionGroup(project));
     setToolbar(toolbarPanel);
 
-    myContentPanel.add(TASK_INFO_ID, createTaskInfoPanel(project));
+    final JPanel panel = new JPanel(new BorderLayout());
+    final Course course = StudyTaskManager.getInstance(project).getCourse();
+    if (course != null && course.isAdaptive()) {
+      panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH);
+    }
+    JComponent taskInfoPanel = createTaskInfoPanel(project);
+    panel.add(taskInfoPanel, BorderLayout.CENTER);
+    myContentPanel.add(TASK_INFO_ID, panel);
     mySplitPane.setFirstComponent(myContentPanel);
     addAdditionalPanels(project);
     myCardLayout.show(myContentPanel, TASK_INFO_ID);
@@ -76,14 +85,15 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
       project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
     }
 
-    if (StudyTaskManager.getInstance(project).isTurnEditingMode() || StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
+    if (StudyTaskManager.getInstance(project).isTurnEditingMode() ||
+        StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
       TaskFile file = StudyUtils.getSelectedTaskFile(project);
       if (file != null) {
         VirtualFile taskDir = file.getTask().getTaskDir(project);
         setTaskText(taskText, taskDir, project);
-
       }
-    } else {
+    }
+    else {
       setTaskText(taskText, null, project);
     }
   }
@@ -98,6 +108,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
     }
   }
 
+
   public void dispose() {
   }
 
@@ -180,7 +191,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D
     }
   }
 
-  protected abstract void setText(String text);
+  protected abstract void setText(@NotNull String text);
 
   public void setEmptyText(@NotNull Project project) {
     if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
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 bbd07b593eaeeabee1da043dc63737671e3c6b2d..5fff6115855f89dcc70fcfebd6c6f4934f66a94a 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" />
     <orderEntry type="library" name="markdownj" level="project" />
   </component>
index 687bdd580a95062e980762d0f5ceae79357df4df..c858ac375be06c15c946406d291fdc5e418d3667 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,28 @@ def get_answer_placeholders():
     return windows
 
 
+def check_samples(samples=()):
+    """
+      Check script output for all samples. Sample is a two element list, where the first is input and
+      the second is output.
+    """
+    for sample in samples:
+        if len(sample) == 2:
+            output = get_file_output(arg_string=str(sample[0]))
+            if "\n".join(output) != sample[1]:
+                failed(
+                    "Test from samples failed: \n \n"
+                    "Input:\n{}"
+                    "\n \n"
+                    "Expected:\n{}"
+                    "\n \n"
+                    "Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output)))
+                return
+        set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.")
+
+    passed()
+
+
 def run_common_tests(error_text="Please, reload file and try again"):
     test_is_initial_text()
     test_is_not_empty()
index 7fb403665248f1e345da751c62b0f585a167d011..60e91394b8ab251ab30db2d704b6cb0568b1931d 100644 (file)
@@ -15,12 +15,13 @@ import com.jetbrains.edu.learning.actions.StudyRunAction;
 import com.jetbrains.edu.learning.checker.StudyCheckTask;
 import com.jetbrains.edu.learning.checker.StudyCheckUtils;
 import com.jetbrains.edu.learning.checker.StudyTestRunner;
-import com.jetbrains.edu.learning.checker.StudyTestsOutputParser;
 import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.editor.StudyEditor;
+import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -41,8 +42,8 @@ public class PyStudyCheckAction extends StudyCheckAction {
       }
       if (StudyCheckUtils.hasBackgroundProcesses(project)) return;
 
-
-      if (!runTask(project)) return;
+      final Course course = StudyTaskManager.getInstance(project).getCourse();
+      if (course != null && !course.isAdaptive() && !runTask(project)) return;
 
       final Task task = studyState.getTask();
       final VirtualFile taskDir = studyState.getTaskDir();
@@ -90,34 +91,48 @@ public class PyStudyCheckAction extends StudyCheckAction {
                                       final Process testProcess,
                                       final String commandLine) {
     return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) {
-            @Override
-            protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) {
-              ApplicationManager.getApplication().invokeLater(() -> {
-                if (myTaskDir == null) return;
-                myTaskManger.setStatus(myTask, StudyStatus.Failed);
-                for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
-                  final String name = entry.getKey();
-                  final TaskFile taskFile = entry.getValue();
-                  if (taskFile.getAnswerPlaceholders().size() < 2) {
-                    myTaskManger.setStatus(taskFile, StudyStatus.Failed);
-                    continue;
-                  }
-                  if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
-                    CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project)));
-                  }
-                }
-                StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project);
-                StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
-              });
+      @Override
+      protected void onTaskFailed(String message) {
+        ApplicationManager.getApplication().invokeLater(() -> {
+          if (myTaskDir == null) return;
+          myTaskManger.setStatus(myTask, StudyStatus.Failed);
+          for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
+            final String name = entry.getKey();
+            final TaskFile taskFile = entry.getValue();
+            if (taskFile.getAnswerPlaceholders().size() < 2) {
+              myTaskManger.setStatus(taskFile, StudyStatus.Failed);
+              continue;
+            }
+            if (EduNames.STUDY.equals(myTaskManger.getCourse().getCourseMode())) {
+              CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> {
+                StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project);
+              }));
+            }
+          }
+          final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project);
+          if (toolWindow != null) {
+            final Course course = StudyTaskManager.getInstance(project).getCourse();
+            if (course != null) {
+              if (course.isAdaptive()) {
+                StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), project);
+                StudyCheckUtils.showTestResultsToolWindow(project, message, false);
+              }
+              else {
+                StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project);
+              }
             }
-          };
+            StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project);
+          }
+        });
+      }
+    };
   }
 
 
   @Nullable
   private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState,
-                                         @NotNull final Task task,
-                                         @NotNull final VirtualFile taskDir) {
+                                                @NotNull final Task task,
+                                                @NotNull final VirtualFile taskDir) {
     VirtualFile taskVirtualFile = studyState.getVirtualFile();
     for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
       String name = entry.getKey();
@@ -131,7 +146,7 @@ public class PyStudyCheckAction extends StudyCheckAction {
     }
     return taskVirtualFile;
   }
-  
+
   @NotNull
   @Override
   public String getActionId() {
index df9db88d18aeb654bd9970d725c761eea87b1214..a3492b7247b266502831e624a86ba9b9cb4f8299 100644 (file)
@@ -6,17 +6,19 @@ import com.intellij.facet.ui.ValidationResult;
 import com.intellij.ide.fileTemplates.FileTemplate;
 import com.intellij.ide.fileTemplates.FileTemplateManager;
 import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.platform.DirectoryProjectGenerator;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiManager;
 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
-import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
 import com.jetbrains.edu.learning.stepic.CourseInfo;
+import com.jetbrains.edu.learning.ui.StudyNewProjectPanel;
 import com.jetbrains.python.newProject.PythonProjectGenerator;
 import icons.InteractiveLearningPythonIcons;
 import org.jetbrains.annotations.Nls;
@@ -89,14 +91,14 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp
         throw new UnsupportedOperationException();
       }
       public void validate() {
-        fireStateChanged();
+        ApplicationManager.getApplication().invokeLater(()->fireStateChanged());
       }
     });
     return settingsPanel.getContentPanel();
   }
 
   public List<CourseInfo> getCourses() {
-    return myGenerator.getCourses(false);
+    return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject());
   }
 
   public void setSelectedCourse(CourseInfo course) {