1 package com.jetbrains.edu.learning.stepic;
3 import com.google.gson.FieldNamingPolicy;
4 import com.google.gson.Gson;
5 import com.google.gson.GsonBuilder;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.application.ModalityState;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.ProgressManager;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.ui.DialogWrapper;
13 import com.intellij.openapi.util.io.FileUtil;
14 import com.intellij.openapi.util.text.StringUtil;
15 import com.intellij.openapi.vfs.VfsUtil;
16 import com.intellij.openapi.vfs.VirtualFile;
17 import com.intellij.openapi.vfs.VirtualFileFilter;
18 import com.intellij.util.net.ssl.CertificateManager;
19 import com.jetbrains.edu.learning.StudySerializationUtils;
20 import com.jetbrains.edu.learning.StudyTaskManager;
21 import com.jetbrains.edu.learning.core.EduNames;
22 import com.jetbrains.edu.learning.core.EduUtils;
23 import com.jetbrains.edu.learning.courseFormat.*;
24 import org.apache.commons.codec.binary.Base64;
25 import org.apache.http.*;
26 import org.apache.http.client.entity.UrlEncodedFormEntity;
27 import org.apache.http.client.methods.*;
28 import org.apache.http.client.utils.URIBuilder;
29 import org.apache.http.cookie.Cookie;
30 import org.apache.http.entity.ContentType;
31 import org.apache.http.entity.StringEntity;
32 import org.apache.http.impl.DefaultConnectionReuseStrategy;
33 import org.apache.http.impl.client.BasicCookieStore;
34 import org.apache.http.impl.client.CloseableHttpClient;
35 import org.apache.http.impl.client.HttpClientBuilder;
36 import org.apache.http.impl.client.HttpClients;
37 import org.apache.http.message.BasicHeader;
38 import org.apache.http.message.BasicNameValuePair;
39 import org.apache.http.util.EntityUtils;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import javax.net.ssl.SSLContext;
44 import javax.net.ssl.TrustManager;
45 import javax.net.ssl.X509TrustManager;
46 import java.io.IOException;
48 import java.net.URISyntaxException;
49 import java.security.KeyManagementException;
50 import java.security.NoSuchAlgorithmException;
51 import java.security.SecureRandom;
52 import java.security.cert.X509Certificate;
55 public class EduStepicConnector {
56 private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName());
57 private static final String stepicUrl = "https://stepic.org/";
58 private static String ourCSRFToken = "";
59 private static CloseableHttpClient ourClient;
61 //this prefix indicates that course can be opened by educational plugin
62 public static final String PYCHARM_PREFIX = "pycharm";
63 private static BasicCookieStore ourCookieStore;
65 private EduStepicConnector() {
68 public static StepicUser login(@NotNull final String username, @NotNull final String password) {
70 if (postCredentials(username, password)) {
71 final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser();
72 if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) {
73 return stepicUserWrapper.users.get(0);
80 public static List<Integer> getEnrolledCoursesIds() {
82 final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build();
83 final List<CourseInfo> courses = getFromStepic(enrolledCoursesUri.toString(), StepicWrappers.CoursesContainer.class).courses;
84 final ArrayList<Integer> ids = new ArrayList<>();
85 for (CourseInfo course : courses) {
86 ids.add(course.getId());
90 catch (IOException e) {
91 LOG.warn(e.getMessage());
93 catch (URISyntaxException e) {
94 LOG.warn(e.getMessage());
96 return Collections.emptyList();
100 public static StepicWrappers.AuthorWrapper getCurrentUser() {
102 return getFromStepic(EduStepicNames.CURRENT_USER, StepicWrappers.AuthorWrapper.class);
104 catch (IOException e) {
105 LOG.warn("Couldn't get author info");
110 public static boolean createUser(@NotNull final String user, @NotNull final String password) {
111 final HttpPost userRequest = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.USERS);
113 setHeaders(userRequest, "application/json");
114 String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password));
115 userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
118 final CloseableHttpResponse response = ourClient.execute(userRequest);
119 final HttpEntity responseEntity = response.getEntity();
120 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
121 final StatusLine statusLine = response.getStatusLine();
122 if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
123 LOG.error("Failed to create user " + responseString);
127 catch (IOException e) {
128 LOG.error(e.getMessage());
133 public static void initializeClient() {
134 if (ourClient == null) {
135 final HttpGet request = new HttpGet(EduStepicNames.STEPIC_URL);
136 request.addHeader(new BasicHeader("referer", EduStepicNames.STEPIC_URL));
137 request.addHeader(new BasicHeader("content-type", EduStepicNames.CONTENT_TYPE_APPL_JSON));
140 HttpClientBuilder builder =
141 HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000).
142 setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE);
143 ourCookieStore = new BasicCookieStore();
146 // Create a trust manager that does not validate certificate for this connection
147 TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
148 public X509Certificate[] getAcceptedIssuers() {
152 public void checkClientTrusted(X509Certificate[] certs, String authType) {
155 public void checkServerTrusted(X509Certificate[] certs, String authType) {
158 SSLContext sslContext = SSLContext.getInstance("TLS");
159 sslContext.init(null, trustAllCerts, new SecureRandom());
160 ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build();
162 ourClient.execute(request);
165 catch (IOException e) {
166 LOG.error(e.getMessage());
168 catch (NoSuchAlgorithmException e) {
169 LOG.error(e.getMessage());
171 catch (KeyManagementException e) {
172 LOG.error(e.getMessage());
177 private static void saveCSRFToken() {
178 if (ourCookieStore == null) return;
179 final List<Cookie> cookies = ourCookieStore.getCookies();
180 for (Cookie cookie : cookies) {
181 if (cookie.getName().equals("csrftoken")) {
182 ourCSRFToken = cookie.getValue();
187 private static boolean postCredentials(String user, String password) {
188 String url = EduStepicNames.STEPIC_URL + EduStepicNames.LOGIN;
189 final HttpPost request = new HttpPost(url);
190 List <NameValuePair> nvps = new ArrayList <NameValuePair>();
191 nvps.add(new BasicNameValuePair("csrfmiddlewaretoken", ourCSRFToken));
192 nvps.add(new BasicNameValuePair("login", user));
193 nvps.add(new BasicNameValuePair("next", "/"));
194 nvps.add(new BasicNameValuePair("password", password));
195 nvps.add(new BasicNameValuePair("remember", "on"));
197 request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
199 setHeaders(request, "application/x-www-form-urlencoded");
202 final CloseableHttpResponse response = ourClient.execute(request);
204 final StatusLine line = response.getStatusLine();
205 if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
206 final HttpEntity responseEntity = response.getEntity();
207 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
208 LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase());
209 LOG.debug("Failed to login " + responseString);
214 catch (IOException e) {
215 LOG.warn(e.getMessage());
222 static <T> T getFromStepic(String link, final Class<T> container) throws IOException {
223 if (!link.startsWith("/")) link = "/" + link;
224 final HttpGet request = new HttpGet(EduStepicNames.STEPIC_API_URL + link);
225 if (ourClient == null) {
228 setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
230 final CloseableHttpResponse response = ourClient.execute(request);
231 final StatusLine statusLine = response.getStatusLine();
232 final HttpEntity responseEntity = response.getEntity();
233 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
234 if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
235 throw new IOException("Stepic returned non 200 status code " + responseString);
237 Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()).
238 registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).
239 setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
240 return gson.fromJson(responseString, container);
244 public static CloseableHttpClient getHttpClient(@NotNull final Project project) {
245 if (ourClient == null) {
252 public static boolean enrollToCourse(final int courseId) {
253 HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ENROLLMENTS);
255 final StepicWrappers.EnrollmentWrapper enrollment = new StepicWrappers.EnrollmentWrapper(String.valueOf(courseId));
256 post.setEntity(new StringEntity(new GsonBuilder().create().toJson(enrollment)));
257 setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
258 if (ourClient == null) {
261 CloseableHttpResponse response = ourClient.execute(post);
262 StatusLine line = response.getStatusLine();
263 return line.getStatusCode() == HttpStatus.SC_CREATED;
265 catch (IOException e) {
266 LOG.warn(e.getMessage());
272 public static List<CourseInfo> getCourses() {
274 List<CourseInfo> result = new ArrayList<CourseInfo>();
276 while (addCoursesFromStepic(result, pageNumber)) {
281 catch (IOException e) {
282 LOG.error("Cannot load course list " + e.getMessage());
284 return Collections.singletonList(CourseInfo.INVALID_COURSE);
287 private static boolean addCoursesFromStepic(List<CourseInfo> result, int pageNumber) throws IOException {
288 final String url = pageNumber == 0 ? EduStepicNames.COURSES : EduStepicNames.COURSES_FROM_PAGE + String.valueOf(pageNumber);
289 final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class);
290 final List<CourseInfo> courseInfos = coursesContainer.courses;
291 for (CourseInfo info : courseInfos) {
292 final String courseType = info.getType();
293 if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue;
294 final List<String> typeLanguage = StringUtil.split(courseType, " ");
295 // TODO: should adaptive course be of PyCharmType ?
296 if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) {
297 for (Integer instructor : info.instructors) {
298 final StepicUser author = getFromStepic(EduStepicNames.USERS + "/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0);
299 info.addAuthor(author);
302 String name = info.getName().replaceAll("[^a-zA-Z0-9\\s]", "");
303 info.setName(name.trim());
308 return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE;
311 public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) {
312 final Course course = new Course();
313 course.setAuthors(info.getAuthors());
314 course.setDescription(info.getDescription());
315 course.setAdaptive(info.isAdaptive());
316 course.setId(info.id);
317 course.setUpToDate(true); // TODO: get from stepic
319 if (!course.isAdaptive()) {
320 String courseType = info.getType();
321 course.setName(info.getName());
322 course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1));
324 for (Integer section : info.sections) {
325 course.addLessons(getLessons(section));
329 catch (IOException e) {
330 LOG.error("IOException " + e.getMessage());
334 final Lesson lesson = new Lesson();
335 course.setName(info.getName());
336 //TODO: more specific name?
337 lesson.setName("Adaptive");
338 course.addLesson(lesson);
339 final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course);
340 if (recommendation != null) {
341 lesson.addTask(recommendation);
351 public static List<Lesson> getLessons(int sectionId) throws IOException {
352 final StepicWrappers.SectionContainer
353 sectionContainer = getFromStepic(EduStepicNames.SECTIONS + String.valueOf(sectionId), StepicWrappers.SectionContainer.class);
354 List<Integer> unitIds = sectionContainer.sections.get(0).units;
355 final List<Lesson> lessons = new ArrayList<Lesson>();
356 for (Integer unitId : unitIds) {
357 StepicWrappers.UnitContainer
358 unit = getFromStepic(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class);
359 int lessonID = unit.units.get(0).lesson;
360 StepicWrappers.LessonContainer
361 lesson = getFromStepic(EduStepicNames.LESSONS + String.valueOf(lessonID), StepicWrappers.LessonContainer.class);
362 Lesson realLesson = lesson.lessons.get(0);
363 realLesson.taskList = new ArrayList<Task>();
364 for (Integer s : realLesson.steps) {
365 createTask(realLesson, s);
367 if (!realLesson.taskList.isEmpty())
368 lessons.add(realLesson);
374 private static void createTask(Lesson lesson, Integer stepicId) throws IOException {
375 final StepicWrappers.Step step = getStep(stepicId);
376 if (!step.name.equals(PYCHARM_PREFIX)) return;
377 final Task task = new Task();
378 task.setStepicId(stepicId);
379 task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX);
380 task.setText(step.text);
381 for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
382 task.addTestsTexts(wrapper.name, wrapper.text);
385 task.taskFiles = new HashMap<String, TaskFile>(); // TODO: it looks like we don't need taskFiles as map anymore
386 if (step.options.files != null) {
387 for (TaskFile taskFile : step.options.files) {
388 task.taskFiles.put(taskFile.name, taskFile);
391 lesson.taskList.add(task);
394 public static StepicWrappers.Step getStep(Integer step) throws IOException {
395 return getFromStepic(EduStepicNames.STEPS + "/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block;
399 public static boolean showLoginDialog() {
400 final boolean[] logged = {false};
401 ApplicationManager.getApplication().invokeAndWait(() -> {
402 final LoginDialog dialog = new LoginDialog();
404 logged[0] = dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE;
405 }, ModalityState.defaultModalityState());
409 public static void postAttempt(@NotNull final Task task, boolean passed, @Nullable String login, @Nullable String password) {
410 if (task.getStepicId() <= 0) {
413 if (ourClient == null) {
414 if (StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)) {
418 if (login(login, password) == null) return;
422 final HttpPost attemptRequest = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS);
423 setHeaders(attemptRequest, "application/json");
424 String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId()));
425 attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON));
428 final CloseableHttpResponse attemptResponse = ourClient.execute(attemptRequest);
429 final HttpEntity responseEntity = attemptResponse.getEntity();
430 final String attemptResponseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
431 final StatusLine statusLine = attemptResponse.getStatusLine();
432 if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) {
433 LOG.error("Failed to make attempt " + attemptResponseString);
435 final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0);
437 final Map<String, TaskFile> taskFiles = task.getTaskFiles();
438 final ArrayList<StepicWrappers.SolutionFile> files = new ArrayList<StepicWrappers.SolutionFile>();
439 for (TaskFile fileEntry : taskFiles.values()) {
440 files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text));
442 postSubmission(passed, attempt, files);
444 catch (IOException e) {
445 LOG.error(e.getMessage());
449 private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList<StepicWrappers.SolutionFile> files) throws IOException {
450 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS);
451 setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
453 String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files));
454 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
455 final CloseableHttpResponse response = ourClient.execute(request);
456 final HttpEntity responseEntity = response.getEntity();
457 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
458 final StatusLine line = response.getStatusLine();
459 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
460 LOG.error("Failed to make submission " + responseString);
464 public static void postCourseWithProgress(final Project project, @NotNull final Course course) {
465 postCourseWithProgress(project, course, false);
468 public static void postCourseWithProgress(final Project project, @NotNull final Course course, final boolean relogin) {
469 ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) {
471 public void run(@NotNull final ProgressIndicator indicator) {
472 postCourse(project, course, relogin, indicator);
477 private static void postCourse(final Project project, @NotNull Course course, boolean relogin, @NotNull final ProgressIndicator indicator) {
478 indicator.setText("Uploading course to " + stepicUrl);
479 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses");
480 if (ourClient == null || !relogin) {
481 if (!login(project)) return;
483 final StepicWrappers.AuthorWrapper user = getCurrentUser();
485 course.setAuthors(user.users);
488 setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
489 String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
490 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
493 final CloseableHttpResponse response = ourClient.execute(request);
494 final HttpEntity responseEntity = response.getEntity();
495 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
496 final StatusLine line = response.getStatusLine();
497 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
500 postCourse(project, course, true, indicator);
502 LOG.error("Failed to push " + responseString);
505 final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
507 final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName()));
509 for (Lesson lesson : course.getLessons()) {
510 indicator.checkCanceled();
511 final int lessonId = postLesson(project, lesson, indicator);
512 postUnit(lessonId, position, sectionId);
515 ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
517 catch (IOException e) {
518 LOG.error(e.getMessage());
522 private static boolean login(@NotNull final Project project) {
523 final StepicUser user = StudyTaskManager.getInstance(project).getUser();
524 final String login = user.getEmail();
525 if (StringUtil.isEmptyOrSpaces(login)) {
526 return showLoginDialog();
529 if (login(login, user.getPassword()) == null) {
530 return showLoginDialog();
536 private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) {
537 final VirtualFile baseDir = project.getBaseDir();
538 final List<VirtualFile> files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
540 public boolean accept(VirtualFile file) {
541 final String name = file.getName();
542 return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) &&
543 !"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith("");
547 if (!files.isEmpty()) {
548 final int sectionId = postModule(id, 2, EduNames.PYCHARM_ADDITIONAL);
549 final Lesson lesson = new Lesson();
550 lesson.setName(EduNames.PYCHARM_ADDITIONAL);
551 final Task task = new Task();
552 task.setLesson(lesson);
553 task.setName(EduNames.PYCHARM_ADDITIONAL);
555 task.setText(EduNames.PYCHARM_ADDITIONAL);
556 for (VirtualFile file : files) {
559 if (EduUtils.isImage(file.getName())) {
560 task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
563 task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
567 catch (IOException e) {
568 LOG.error("Can't find file " + file.getPath());
571 lesson.addTask(task);
573 final int lessonId = postLesson(project, lesson, indicator);
574 postUnit(lessonId, 1, sectionId);
578 private static void postUnit(int lessonId, int position, int sectionId) {
579 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.UNITS);
580 setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
581 final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
582 unitWrapper.unit = new StepicWrappers.Unit();
583 unitWrapper.unit.lesson = lessonId;
584 unitWrapper.unit.position = position;
585 unitWrapper.unit.section = sectionId;
587 String requestBody = new Gson().toJson(unitWrapper);
588 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
591 final CloseableHttpResponse response = ourClient.execute(request);
592 final HttpEntity responseEntity = response.getEntity();
593 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
594 final StatusLine line = response.getStatusLine();
595 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
596 LOG.error("Failed to push " + responseString);
599 catch (IOException e) {
600 LOG.error(e.getMessage());
604 private static int postModule(int courseId, int position, @NotNull final String title) {
605 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/sections");
606 setHeaders(request, "application/json");
607 final StepicWrappers.Section section = new StepicWrappers.Section();
608 section.course = courseId;
609 section.title = title;
610 section.position = position;
611 final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
612 sectionContainer.section = section;
613 String requestBody = new Gson().toJson(sectionContainer);
614 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
617 final CloseableHttpResponse response = ourClient.execute(request);
618 final HttpEntity responseEntity = response.getEntity();
619 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
620 final StatusLine line = response.getStatusLine();
621 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
622 LOG.error("Failed to push " + responseString);
624 final StepicWrappers.Section
625 postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
626 return postedSection.id;
628 catch (IOException e) {
629 LOG.error(e.getMessage());
634 public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
635 final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
636 if (ourClient == null) {
637 if (!login(project)) {
638 LOG.error("Failed to push lesson");
643 setHeaders(request, "application/json");
644 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
645 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
648 final CloseableHttpResponse response = ourClient.execute(request);
649 final HttpEntity responseEntity = response.getEntity();
650 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
651 final StatusLine line = response.getStatusLine();
652 if (line.getStatusCode() != HttpStatus.SC_OK) {
653 LOG.error("Failed to push " + responseString);
656 final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
657 for (Integer step : postedLesson.steps) {
661 for (Task task : lesson.getTaskList()) {
662 indicator.checkCanceled();
663 postTask(project, task, lesson.getId());
665 return lesson.getId();
667 catch (IOException e) {
668 LOG.error(e.getMessage());
673 public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
674 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
675 if (ourClient == null) {
679 setHeaders(request, "application/json");
680 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
681 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
684 final CloseableHttpResponse response = ourClient.execute(request);
685 final HttpEntity responseEntity = response.getEntity();
686 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
687 final StatusLine line = response.getStatusLine();
688 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
689 LOG.error("Failed to push " + responseString);
692 final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
693 lesson.setId(postedLesson.getId());
694 for (Task task : lesson.getTaskList()) {
695 indicator.checkCanceled();
696 postTask(project, task, postedLesson.getId());
698 return postedLesson.getId();
700 catch (IOException e) {
701 LOG.error(e.getMessage());
706 public static void deleteTask(@NotNull final Integer task) {
707 final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task);
708 setHeaders(request, "application/json");
709 ApplicationManager.getApplication().invokeLater(() -> {
711 final CloseableHttpResponse response = ourClient.execute(request);
712 final StatusLine line = response.getStatusLine();
713 if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
714 final HttpEntity responseEntity = response.getEntity();
715 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
716 LOG.error("Failed to delete task " + responseString);
719 catch (IOException e) {
720 LOG.error(e.getMessage());
725 public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
726 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources");
727 setHeaders(request, "application/json");
728 //TODO: register type adapter for task files here?
729 final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
730 registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
731 ApplicationManager.getApplication().invokeLater(() -> {
732 final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
733 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
736 final CloseableHttpResponse response = ourClient.execute(request);
737 final StatusLine line = response.getStatusLine();
738 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
739 final HttpEntity responseEntity = response.getEntity();
740 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
741 LOG.error("Failed to push " + responseString);
744 catch (IOException e) {
745 LOG.error(e.getMessage());
750 static void setHeaders(@NotNull final HttpRequestBase request, String contentType) {
751 request.addHeader(new BasicHeader("referer", stepicUrl));
752 request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken));
753 request.addHeader(new BasicHeader("content-type", contentType));