replace subtaskInfo map with list for stepik
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / stepic / CCStepicConnector.java
1 package com.jetbrains.edu.learning.stepic;
2
3 import com.google.gson.Gson;
4 import com.google.gson.GsonBuilder;
5 import com.google.gson.JsonObject;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.progress.ProgressIndicator;
9 import com.intellij.openapi.progress.ProgressManager;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.util.io.FileUtil;
12 import com.intellij.openapi.vfs.VfsUtil;
13 import com.intellij.openapi.vfs.VirtualFile;
14 import com.intellij.openapi.vfs.VirtualFileFilter;
15 import com.jetbrains.edu.learning.StudySerializationUtils;
16 import com.jetbrains.edu.learning.core.EduNames;
17 import com.jetbrains.edu.learning.core.EduUtils;
18 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
19 import com.jetbrains.edu.learning.courseFormat.Course;
20 import com.jetbrains.edu.learning.courseFormat.Lesson;
21 import com.jetbrains.edu.learning.courseFormat.Task;
22 import org.apache.commons.codec.binary.Base64;
23 import org.apache.http.HttpEntity;
24 import org.apache.http.HttpStatus;
25 import org.apache.http.StatusLine;
26 import org.apache.http.client.methods.CloseableHttpResponse;
27 import org.apache.http.client.methods.HttpDelete;
28 import org.apache.http.client.methods.HttpPost;
29 import org.apache.http.client.methods.HttpPut;
30 import org.apache.http.entity.ContentType;
31 import org.apache.http.entity.StringEntity;
32 import org.apache.http.impl.client.CloseableHttpClient;
33 import org.apache.http.util.EntityUtils;
34 import org.jetbrains.annotations.NotNull;
35
36 import java.io.IOException;
37 import java.util.Collections;
38 import java.util.List;
39
40 public class CCStepicConnector {
41   private static final Logger LOG = Logger.getInstance(CCStepicConnector.class.getName());
42
43   private CCStepicConnector() {
44   }
45
46   public static CourseInfo getCourseInfo(Project project, String courseId) {
47     final String url = EduStepicNames.COURSES + "/" + courseId;
48     try {
49       final StepicWrappers.CoursesContainer coursesContainer =
50         EduStepicAuthorizedClient.getFromStepic(url, StepicWrappers.CoursesContainer.class, project);
51       return coursesContainer.courses.get(0);
52     }
53     catch (IOException e) {
54       LOG.error(e.getMessage());
55     }
56     return null;
57   }
58
59   public static void postCourseWithProgress(final Project project, @NotNull final Course course) {
60     ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) {
61       @Override
62       public void run(@NotNull final ProgressIndicator indicator) {
63         postCourse(project, course, indicator);
64       }
65     });
66   }
67
68   private static void postCourse(final Project project, @NotNull Course course, @NotNull final ProgressIndicator indicator) {
69     indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL);
70     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses");
71
72     final StepicUser currentUser = EduStepicAuthorizedClient.getCurrentUser();
73     if (currentUser != null) {
74       final List<StepicUser> courseAuthors = course.getAuthors();
75       for (int i = 0; i < courseAuthors.size(); i++) {
76         if (courseAuthors.size() > i) {
77           final StepicUser courseAuthor = courseAuthors.get(i);
78           currentUser.setFirstName(courseAuthor.getFirstName());
79           currentUser.setLastName(courseAuthor.getLastName());
80         }
81       }
82       course.setAuthors(Collections.singletonList(currentUser));
83     }
84
85     String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
86     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
87
88     try {
89       final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
90       final CloseableHttpResponse response = client.execute(request);
91       final HttpEntity responseEntity = response.getEntity();
92       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
93       final StatusLine line = response.getStatusLine();
94       EntityUtils.consume(responseEntity);
95       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
96         LOG.error("Failed to push " + responseString);
97         return;
98       }
99       final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0);
100       course.setId(postedCourse.id);
101       final int sectionId = postModule(project, postedCourse.id, 1, String.valueOf(postedCourse.getName()));
102       int position = 1;
103       for (Lesson lesson : course.getLessons()) {
104         indicator.checkCanceled();
105         final int lessonId = postLesson(project, lesson, indicator);
106         postUnit(project, lessonId, position, sectionId);
107         position += 1;
108       }
109       ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
110     }
111     catch (IOException e) {
112       LOG.error(e.getMessage());
113     }
114   }
115
116   private static void postAdditionalFiles(@NotNull final Project project, int id, ProgressIndicator indicator) {
117     final VirtualFile baseDir = project.getBaseDir();
118     final List<VirtualFile> files = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
119       @Override
120       public boolean accept(VirtualFile file) {
121         final String name = file.getName();
122         return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) &&
123                !"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith(".");
124       }
125     });
126
127     if (!files.isEmpty()) {
128       final int sectionId = postModule(project, id, 2, EduNames.PYCHARM_ADDITIONAL);
129       final Lesson lesson = new Lesson();
130       lesson.setName(EduNames.PYCHARM_ADDITIONAL);
131       final Task task = new Task();
132       task.setLesson(lesson);
133       task.setName(EduNames.PYCHARM_ADDITIONAL);
134       task.setIndex(1);
135       task.setText(EduNames.PYCHARM_ADDITIONAL);
136       for (VirtualFile file : files) {
137         try {
138           if (file != null) {
139             if (EduUtils.isImage(file.getName())) {
140               task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
141             }
142             else {
143               task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
144             }
145           }
146         }
147         catch (IOException e) {
148           LOG.error("Can't find file " + file.getPath());
149         }
150       }
151       lesson.addTask(task);
152       lesson.setIndex(1);
153       final int lessonId = postLesson(project, lesson, indicator);
154       postUnit(project, lessonId, 1, sectionId);
155     }
156   }
157
158   public static void postUnit(@NotNull final Project project, int lessonId, int position, int sectionId) {
159     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.UNITS);
160     final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper();
161     unitWrapper.unit = new StepicWrappers.Unit();
162     unitWrapper.unit.lesson = lessonId;
163     unitWrapper.unit.position = position;
164     unitWrapper.unit.section = sectionId;
165
166     String requestBody = new Gson().toJson(unitWrapper);
167     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
168
169     try {
170       final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
171       final CloseableHttpResponse response = client.execute(request);
172       final HttpEntity responseEntity = response.getEntity();
173       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
174       final StatusLine line = response.getStatusLine();
175       EntityUtils.consume(responseEntity);
176       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
177         LOG.error("Failed to push " + responseString);
178       }
179     }
180     catch (IOException e) {
181       LOG.error(e.getMessage());
182     }
183   }
184
185   private static int postModule(@NotNull final Project project, int courseId, int position, @NotNull final String title) {
186     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/sections");
187     final StepicWrappers.Section section = new StepicWrappers.Section();
188     section.course = courseId;
189     section.title = title;
190     section.position = position;
191     final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper();
192     sectionContainer.section = section;
193     String requestBody = new Gson().toJson(sectionContainer);
194     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
195
196     try {
197       final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
198       final CloseableHttpResponse response = client.execute(request);
199       final HttpEntity responseEntity = response.getEntity();
200       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
201       final StatusLine line = response.getStatusLine();
202       EntityUtils.consume(responseEntity);
203       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
204         LOG.error("Failed to push " + responseString);
205         return -1;
206       }
207       final StepicWrappers.Section
208         postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
209       return postedSection.id;
210     }
211     catch (IOException e) {
212       LOG.error(e.getMessage());
213     }
214     return -1;
215   }
216
217   public static int updateTask(@NotNull final Project project, @NotNull final Task task) {
218     final Lesson lesson = task.getLesson();
219     final int lessonId = lesson.getId();
220
221     final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepId()));
222     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
223     ApplicationManager.getApplication().invokeLater(() -> {
224       task.addTestsTexts("tests.py", task.getTestsText(project));
225       final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
226       request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
227
228       try {
229         final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
230         final CloseableHttpResponse response = client.execute(request);
231         final HttpEntity responseEntity = response.getEntity();
232         final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
233         EntityUtils.consume(responseEntity);
234         final StatusLine line = response.getStatusLine();
235         if (line.getStatusCode() != HttpStatus.SC_OK) {
236           LOG.error("Failed to push " + responseString);
237         }
238       }
239       catch (IOException e) {
240         LOG.error(e.getMessage());
241       }
242     });
243     return -1;
244   }
245
246   public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
247     final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
248
249     String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
250     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
251
252     try {
253       final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
254       final CloseableHttpResponse response = client.execute(request);
255       final HttpEntity responseEntity = response.getEntity();
256       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
257       final StatusLine line = response.getStatusLine();
258       EntityUtils.consume(responseEntity);
259       if (line.getStatusCode() != HttpStatus.SC_OK) {
260         LOG.error("Failed to push " + responseString);
261         return -1;
262       }
263       final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
264       for (Integer step : postedLesson.steps) {
265         deleteTask(step, project);
266       }
267
268       for (Task task : lesson.getTaskList()) {
269         indicator.checkCanceled();
270         postTask(project, task, lesson.getId());
271       }
272       return lesson.getId();
273     }
274     catch (IOException e) {
275       LOG.error(e.getMessage());
276     }
277     return -1;
278   }
279
280   public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
281     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
282
283     String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
284     request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
285
286     try {
287       final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
288       final CloseableHttpResponse response = client.execute(request);
289       final HttpEntity responseEntity = response.getEntity();
290       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
291       final StatusLine line = response.getStatusLine();
292       EntityUtils.consume(responseEntity);
293       if (line.getStatusCode() != HttpStatus.SC_CREATED) {
294         LOG.error("Failed to push " + responseString);
295         return 0;
296       }
297       final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
298       lesson.setId(postedLesson.getId());
299       for (Task task : lesson.getTaskList()) {
300         indicator.checkCanceled();
301         postTask(project, task, postedLesson.getId());
302       }
303       return postedLesson.getId();
304     }
305     catch (IOException e) {
306       LOG.error(e.getMessage());
307     }
308     return -1;
309   }
310
311   public static void deleteTask(@NotNull final Integer task, Project project) {
312     final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task);
313     ApplicationManager.getApplication().invokeLater(() -> {
314       try {
315         final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
316         final CloseableHttpResponse response = client.execute(request);
317         final HttpEntity responseEntity = response.getEntity();
318         final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
319         EntityUtils.consume(responseEntity);
320         final StatusLine line = response.getStatusLine();
321         if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
322           LOG.error("Failed to delete task " + responseString);
323         }
324       }
325       catch (IOException e) {
326         LOG.error(e.getMessage());
327       }
328     });
329   }
330
331   public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
332     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources");
333     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
334       registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
335     ApplicationManager.getApplication().invokeLater(() -> {
336       final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
337       request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
338
339       try {
340         final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
341         final CloseableHttpResponse response = client.execute(request);
342         final StatusLine line = response.getStatusLine();
343         final HttpEntity responseEntity = response.getEntity();
344         final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
345         EntityUtils.consume(responseEntity);
346         if (line.getStatusCode() != HttpStatus.SC_CREATED) {
347           LOG.error("Failed to push " + responseString);
348           return;
349         }
350
351         final JsonObject postedTask = new Gson().fromJson(responseString, JsonObject.class);
352         final JsonObject stepSource = postedTask.getAsJsonArray("step-sources").get(0).getAsJsonObject();
353         task.setStepId(stepSource.getAsJsonPrimitive("id").getAsInt());
354       }
355       catch (IOException e) {
356         LOG.error(e.getMessage());
357       }
358     });
359   }
360 }