Fix connection closing: close connection in all cases
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / stepic / EduAdaptiveStepicConnector.java
1 package com.jetbrains.edu.learning.stepic;
2
3 import com.google.gson.FieldNamingPolicy;
4 import com.google.gson.Gson;
5 import com.google.gson.GsonBuilder;
6 import com.intellij.ide.projectView.ProjectView;
7 import com.intellij.lang.Language;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.editor.Editor;
12 import com.intellij.openapi.progress.ProgressIndicator;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.projectRoots.Sdk;
15 import com.intellij.openapi.ui.MessageType;
16 import com.intellij.openapi.ui.popup.Balloon;
17 import com.intellij.openapi.ui.popup.JBPopupFactory;
18 import com.intellij.openapi.util.Pair;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vfs.VfsUtil;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.openapi.vfs.VirtualFileManager;
24 import com.jetbrains.edu.learning.StudyTaskManager;
25 import com.jetbrains.edu.learning.StudyUtils;
26 import com.jetbrains.edu.learning.checker.StudyExecutor;
27 import com.jetbrains.edu.learning.core.EduNames;
28 import com.jetbrains.edu.learning.courseFormat.*;
29 import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
30 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
31 import com.jetbrains.edu.learning.editor.StudyEditor;
32 import com.jetbrains.edu.learning.navigation.StudyNavigator;
33 import com.jetbrains.edu.learning.ui.StudyToolWindow;
34 import org.apache.http.HttpEntity;
35 import org.apache.http.HttpStatus;
36 import org.apache.http.client.config.RequestConfig;
37 import org.apache.http.client.methods.CloseableHttpResponse;
38 import org.apache.http.client.methods.HttpGet;
39 import org.apache.http.client.methods.HttpPost;
40 import org.apache.http.client.utils.URIBuilder;
41 import org.apache.http.entity.ContentType;
42 import org.apache.http.entity.StringEntity;
43 import org.apache.http.impl.client.CloseableHttpClient;
44 import org.apache.http.util.EntityUtils;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import java.io.File;
49 import java.io.IOException;
50 import java.io.UnsupportedEncodingException;
51 import java.net.URI;
52 import java.net.URISyntaxException;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.concurrent.TimeUnit;
57
58 import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
59
60 public class EduAdaptiveStepicConnector {
61   public static final String PYTHON2 = "python2";
62   public static final String PYTHON3 = "python3";
63   public static final String PYCHARM_COMMENT = "# Posted from PyCharm Edu\n";
64   private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
65   private static final int CONNECTION_TIMEOUT = 60 * 1000;
66
67   @Nullable
68   public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
69     try {
70       final CloseableHttpClient client = getHttpClient(project);
71       final URI uri = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATIONS_URL)
72         .addParameter(EduNames.COURSE, String.valueOf(course.getId()))
73         .build();
74       final HttpGet request = new HttpGet(uri);
75       setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
76       setTimeout(request);
77
78       final CloseableHttpResponse response = client.execute(request);
79       final HttpEntity responseEntity = response.getEntity();
80       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
81
82       final int statusCode = response.getStatusLine().getStatusCode();
83       EntityUtils.consume(responseEntity);
84       if (statusCode == HttpStatus.SC_OK) {
85         final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
86         final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
87
88         if (recomWrapper.recommendations.length != 0) {
89           final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
90           final String lessonId = recommendation.lesson;
91           final StepicWrappers.LessonContainer
92             lessonContainer = getFromStepic(EduStepicNames.LESSONS + lessonId, StepicWrappers.LessonContainer.class);
93           if (lessonContainer.lessons.size() == 1) {
94             final Lesson realLesson = lessonContainer.lessons.get(0);
95             course.getLessons().get(0).setId(Integer.parseInt(lessonId));
96
97             viewAllSteps(client, realLesson.getId());
98
99             for (int stepId : realLesson.steps) {
100               final StepicWrappers.StepSource step = getStep(stepId);
101               if (step.block.name.equals("code")) {
102                 return getTaskFromStep(project, stepId, step.block, realLesson.getName());
103               }
104             }
105
106             final StepicUser user = StudyTaskManager.getInstance(project).getUser();
107             postRecommendationReaction(project, lessonId,
108                                        String.valueOf(user.getId()), -1);
109             return getNextRecommendation(project, course);
110           }
111           else {
112             LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
113           }
114         }
115       }
116       else {
117         if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
118           return getNextRecommendation(project, course);
119         }
120         throw new IOException("Stepic returned non 200 status code: " + responseString);
121       }
122     }
123     catch (IOException e) {
124       LOG.warn(e.getMessage());
125
126       final String connectionMessages = "Connection problems, Please, try again";
127       final Balloon balloon =
128         JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(connectionMessages, MessageType.ERROR, null)
129           .createBalloon();
130       ApplicationManager.getApplication().invokeLater(() -> {
131         if (StudyUtils.getSelectedEditor(project) != null) {
132           StudyUtils.showCheckPopUp(project, balloon);
133         }
134       });
135     }
136     catch (URISyntaxException e) {
137       LOG.warn(e.getMessage());
138     }
139     return null;
140   }
141
142   private static void setTimeout(HttpGet request) {
143     final RequestConfig requestConfig = RequestConfig.custom()
144       .setConnectionRequestTimeout(CONNECTION_TIMEOUT)
145       .setConnectTimeout(CONNECTION_TIMEOUT)
146       .setSocketTimeout(CONNECTION_TIMEOUT)
147       .build();
148     request.setConfig(requestConfig);
149   }
150
151   private static void setTimeout(HttpPost request) {
152     final RequestConfig requestConfig = RequestConfig.custom()
153       .setConnectionRequestTimeout(CONNECTION_TIMEOUT)
154       .setConnectTimeout(CONNECTION_TIMEOUT)
155       .setSocketTimeout(CONNECTION_TIMEOUT)
156       .build();
157     request.setConfig(requestConfig);
158   }
159
160   private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
161     final URI unitsUrl = new URIBuilder(EduStepicNames.UNITS).addParameter(EduNames.LESSON, String.valueOf(lessonId)).build();
162     final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
163     if (unitContainer.units.size() != 1) {
164       LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
165       return;
166     }
167
168     final URIBuilder builder = new URIBuilder(EduStepicNames.ASSIGNMENT);
169     for (Integer step : unitContainer.units.get(0).assignments) {
170       builder.addParameter("ids[]", String.valueOf(step));
171     }
172     final URI assignmentUrl = builder.build();
173     final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
174     if (assignments.assignments.size() > 0) {
175       for (StepicWrappers.Assignment assignment : assignments.assignments) {
176         final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.VIEWS_URL);
177         final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
178         post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
179         setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
180         setTimeout(post);
181         final CloseableHttpResponse viewPostResult = client.execute(post);
182         if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
183           LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
184         }
185       }
186     }
187     else {
188       LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
189     }
190   }
191
192   public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
193                                                    @NotNull final String user, int reaction) {
194     final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATION_REACTIONS_URL);
195     final String json = new Gson()
196       .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
197     post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
198     final CloseableHttpClient client = getHttpClient(project);
199     setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
200     setTimeout(post);
201     try {
202       final CloseableHttpResponse execute = client.execute(post);
203       final int statusCode = execute.getStatusLine().getStatusCode();
204       final HttpEntity entity = execute.getEntity();
205       final String entityString = EntityUtils.toString(entity);
206       EntityUtils.consume(entity);
207       if (statusCode == HttpStatus.SC_CREATED) {
208         return true;
209       }
210       else {
211         if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
212           return postRecommendationReaction(project, lessonId, user, reaction);
213         }
214         LOG.warn("Stepic returned non-201 status code: " + statusCode + " " + entityString);
215         return false;
216       }
217     }
218     catch (IOException e) {
219       LOG.warn(e.getMessage());
220     }
221     return false;
222   }
223
224   public static void addNextRecommendedTask(@NotNull final Project project, int reaction, ProgressIndicator indicator) {
225     final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
226     final Course course = StudyTaskManager.getInstance(project).getCourse();
227     if (course != null && editor != null && editor.getTaskFile() != null) {
228       indicator.checkCanceled();
229       final StepicUser user = StudyTaskManager.getInstance(project).getUser();
230
231       final boolean recommendationReaction =
232         postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().getId()),
233                                    String.valueOf(user.getId()), reaction);
234       if (recommendationReaction) {
235         indicator.checkCanceled();
236         final Task task = getNextRecommendation(project, course);
237
238         if (task != null) {
239           final Lesson adaptive = course.getLessons().get(0);
240           final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
241           final String lessonName = EduNames.LESSON + String.valueOf(adaptive.getIndex());
242           if (reaction == 0 || reaction == -1) {
243             unsolvedTask.setName(task.getName());
244             unsolvedTask.setStepicId(task.getStepicId());
245             unsolvedTask.setText(task.getText());
246             unsolvedTask.getTestsText().clear();
247             unsolvedTask.setStatus(StudyStatus.Unchecked);
248             final Map<String, String> testsText = task.getTestsText();
249             for (String testName : testsText.keySet()) {
250               unsolvedTask.addTestsTexts(testName, testsText.get(testName));
251             }
252             final Map<String, TaskFile> taskFiles = task.getTaskFiles();
253             if (taskFiles.size() == 1) {
254               final TaskFile taskFile = editor.getTaskFile();
255               taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
256
257               ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
258                 final Document document = editor.getEditor().getDocument();
259                 final String taskFileText = taskFiles.get(EduStepicNames.DEFAULT_TASKFILE_NAME).text;
260                 document.setText(taskFileText);
261               }));
262             }
263             else {
264               LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
265             }
266
267             final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
268             final String taskName = EduNames.TASK + String.valueOf(adaptive.getTaskList().size());
269             final File taskDirectory = new File(lessonDirectory, taskName);
270             StudyProjectGenerator.flushTask(task, taskDirectory);
271             StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
272             final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
273
274             if (lessonDir != null) {
275               createTestFiles(course, task, unsolvedTask, lessonDir);
276             }
277             final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
278             if (window != null) {
279               window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getText()), unsolvedTask.getTaskDir(project), project);
280             }
281             StudyNavigator.navigateToTask(project, lessonName, taskName);
282           }
283           else {
284             adaptive.addTask(task);
285             task.setIndex(adaptive.getTaskList().size());
286             final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
287
288             if (lessonDir != null) {
289               ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
290                 try {
291                   final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
292                   final String taskName = EduNames.TASK + String.valueOf(task.getIndex());
293                   final File taskDir = new File(lessonDirectory, taskName);
294                   StudyProjectGenerator.flushTask(task, taskDir);
295                   StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
296                   StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
297                   adaptive.initLesson(course, true);
298                   StudyNavigator.navigateToTask(project, lessonName, taskName);
299                 }
300                 catch (IOException e) {
301                   LOG.warn(e.getMessage());
302                 }
303               }));
304             }
305           }
306         }
307         ApplicationManager.getApplication().invokeLater(() -> {
308           VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
309           ProjectView.getInstance(project).refresh();
310         });
311       }
312       else {
313         LOG.warn("Recommendation reactions weren't posted");
314         ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project));
315       }
316     }
317   }
318
319   private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
320     ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
321       try {
322         final VirtualFile taskDir = VfsUtil
323           .findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
324         final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
325         File newResourceRoot = null;
326         if (taskDir != null) {
327           newResourceRoot = new File(resourceRoot, taskDir.getName());
328           File[] filesInTask = newResourceRoot.listFiles();
329           if (filesInTask != null) {
330             for (File file : filesInTask) {
331               String fileName = file.getName();
332               if (!task.isTaskFile(fileName)) {
333                 File resourceFile = new File(newResourceRoot, fileName);
334                 File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
335                 FileUtil.copy(resourceFile, fileInProject);
336               }
337             }
338           }
339         }
340         else {
341           LOG.warn("Task directory is null");
342         }
343       }
344       catch (IOException e) {
345         LOG.warn(e.getMessage());
346       }
347     }));
348   }
349
350   @NotNull
351   private static Task getTaskFromStep(Project project, int lessonID, @NotNull final StepicWrappers.Step step, @NotNull String name) {
352     final Task task = new Task();
353     task.setName(name);
354     task.setStepicId(lessonID);
355     task.setText(step.text);
356     task.setStatus(StudyStatus.Unchecked);
357     if (step.options.samples != null) {
358       final StringBuilder builder = new StringBuilder();
359       for (List<String> sample : step.options.samples) {
360         if (sample.size() == 2) {
361           builder.append("<b>Sample Input:</b><br>");
362           builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
363           builder.append("<br>");
364           builder.append("<b>Sample Output:</b><br>");
365           builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
366           builder.append("<br><br>");
367         }
368       }
369       task.setText(task.getText() + "<br>" + builder.toString());
370     }
371
372     if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
373       String builder = "<b>Memory limit</b>: " +
374                        step.options.executionMemoryLimit + " Mb" +
375                        "<br>" +
376                        "<b>Time limit</b>: " +
377                        step.options.executionTimeLimit + "s" +
378                        "<br><br>";
379       task.setText(task.getText() + builder);
380     }
381
382     if (step.options.test != null) {
383       for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
384         task.addTestsTexts(wrapper.name, wrapper.text);
385       }
386     }
387     else {
388       if (step.options.samples != null) {
389         createTestFileFromSamples(task, step.options.samples);
390       }
391     }
392
393     task.taskFiles = new HashMap<String, TaskFile>();      // TODO: it looks like we don't need taskFiles as map anymore
394     if (step.options.files != null) {
395       for (TaskFile taskFile : step.options.files) {
396         task.taskFiles.put(taskFile.name, taskFile);
397       }
398     }
399     else {
400       final TaskFile taskFile = new TaskFile();
401       taskFile.name = "code";
402       final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
403       taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
404       task.taskFiles.put("code.py", taskFile);
405     }
406     return task;
407   }
408
409   private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
410                                                @NotNull final Task task, @NotNull final Project project) {
411     if (codeTemplates != null) {
412       final String languageString = getLanguageString(task, project);
413       if (languageString != null) {
414         return codeTemplates.getTemplateForLanguage(languageString);
415       }
416     }
417
418     return null;
419   }
420
421   @Nullable
422   public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
423     int attemptId = -1;
424     try {
425       attemptId = getAttemptId(project, task);
426     }
427     catch (IOException e) {
428       LOG.warn(e.getMessage());
429     }
430     if (attemptId != -1) {
431       final Editor editor = StudyUtils.getSelectedEditor(project);
432       String language = getLanguageString(task, project);
433       if (editor != null && language != null) {
434         final CloseableHttpClient client = getHttpClient(project);
435         StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
436
437         final StepicUser user = StudyTaskManager.getInstance(project).getUser();
438         final int id = user.getId();
439         wrapper = getCheckResults(attemptId, id, client, wrapper);
440         if (wrapper.submissions.length == 1) {
441           final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
442           return Pair.create(isSolved, wrapper.submissions[0].hint);
443         }
444         else {
445           LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
446         }
447       }
448     }
449     else {
450       LOG.warn("Got an incorrect attempt id: " + attemptId);
451     }
452     return Pair.create(false, "");
453   }
454
455   @Nullable
456   private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
457                                                                             final int attemptId,
458                                                                             @NotNull final String language,
459                                                                             @NotNull final String text) {
460     final CloseableHttpResponse response;
461     try {
462       final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
463         new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, PYCHARM_COMMENT + text);
464       final HttpPost httpPost = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS);
465       setHeaders(httpPost, EduStepicNames.CONTENT_TYPE_APPL_JSON);
466       setTimeout(httpPost);
467       try {
468         httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
469       }
470       catch (UnsupportedEncodingException e) {
471         LOG.warn(e.getMessage());
472       }
473       response = client.execute(httpPost);
474       final HttpEntity entity = response.getEntity();
475       final String entityString = EntityUtils.toString(entity);
476       EntityUtils.consume(entity);
477       return new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class);
478     }
479     catch (IOException e) {
480       LOG.warn(e.getMessage());
481     }
482     return null;
483   }
484
485   @NotNull
486   private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
487                                                                         int id,
488                                                                         CloseableHttpClient client,
489                                                                         StepicWrappers.ResultSubmissionWrapper wrapper) {
490     try {
491       while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
492         TimeUnit.MILLISECONDS.sleep(500);
493         final URI submissionURI = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS)
494           .addParameter("attempt", String.valueOf(attemptId))
495           .addParameter("order", "desc")
496           .addParameter("user", String.valueOf(id))
497           .build();
498         final HttpGet httpGet = new HttpGet(submissionURI);
499         setHeaders(httpGet, EduStepicNames.CONTENT_TYPE_APPL_JSON);
500         setTimeout(httpGet);
501         final CloseableHttpResponse httpResponse = client.execute(httpGet);
502         final HttpEntity entity = httpResponse.getEntity();
503         final String entityString = EntityUtils.toString(entity);
504         EntityUtils.consume(entity);
505         wrapper = new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class);
506       }
507     }
508     catch (InterruptedException e) {
509       LOG.warn(e.getMessage());
510     }
511     catch (IOException e) {
512       LOG.warn(e.getMessage());
513     }
514     catch (URISyntaxException e) {
515       LOG.warn(e.getMessage());
516     }
517     return wrapper;
518   }
519
520   @Nullable
521   private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
522     final Language pythonLanguage = Language.findLanguageByID("Python");
523     if (pythonLanguage != null) {
524       final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
525       if (language != null) {
526         final String versionString = language.getVersionString();
527         if (versionString != null) {
528           final List<String> versionStringParts = StringUtil.split(versionString, " ");
529           if (versionStringParts.size() == 2) {
530             return versionStringParts.get(1).startsWith("2") ? PYTHON2 : PYTHON3;
531           }
532         }
533       }
534       else {
535         StudyUtils.showNoSdkNotification(task, project);
536       }
537     }
538     return null;
539   }
540
541   private static int getAttemptId(@NotNull final Project project, @NotNull Task task) throws IOException {
542     final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
543
544     final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS);
545     post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
546
547     final CloseableHttpClient client = getHttpClient(project);
548     setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
549     setTimeout(post);
550     final CloseableHttpResponse httpResponse = client.execute(post);
551     final int statusCode = httpResponse.getStatusLine().getStatusCode();
552     final HttpEntity entity = httpResponse.getEntity();
553     final String entityString = EntityUtils.toString(entity);
554     EntityUtils.consume(entity);
555     if (statusCode == HttpStatus.SC_CREATED) {
556       final StepicWrappers.AttemptContainer container =
557         new Gson().fromJson(entityString, StepicWrappers.AttemptContainer.class);
558       return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
559     }
560     else {
561       if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
562         return getAttemptId(project, task);
563       }
564     }
565     return -1;
566   }
567
568   private static void createTestFileFromSamples(@NotNull final Task task,
569                                                 @NotNull final List<List<String>> samples) {
570
571     String testText = "from test_helper import check_samples\n\n" +
572                       "if __name__ == '__main__':\n" +
573                       "    check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
574     task.addTestsTexts("tests.py", testText);
575   }
576 }