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