1 package com.jetbrains.edu.learning.stepic;
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;
36 import java.io.IOException;
37 import java.util.Collections;
38 import java.util.List;
40 public class CCStepicConnector {
41 private static final Logger LOG = Logger.getInstance(CCStepicConnector.class.getName());
43 private CCStepicConnector() {
46 public static CourseInfo getCourseInfo(Project project, String courseId) {
47 final String url = EduStepicNames.COURSES + "/" + courseId;
49 final StepicWrappers.CoursesContainer coursesContainer =
50 EduStepicAuthorizedClient.getFromStepic(url, StepicWrappers.CoursesContainer.class, project);
51 return coursesContainer.courses.get(0);
53 catch (IOException e) {
54 LOG.error(e.getMessage());
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) {
62 public void run(@NotNull final ProgressIndicator indicator) {
63 postCourse(project, course, indicator);
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");
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());
82 course.setAuthors(Collections.singletonList(currentUser));
85 String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
86 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
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()));
103 for (Lesson lesson : course.getLessons()) {
104 indicator.checkCanceled();
105 final int lessonId = postLesson(project, lesson, indicator);
106 postUnit(project, lessonId, position, sectionId);
109 ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
111 catch (IOException e) {
112 LOG.error(e.getMessage());
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() {
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(".");
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);
135 task.setText(EduNames.PYCHARM_ADDITIONAL);
136 for (VirtualFile file : files) {
139 if (EduUtils.isImage(file.getName())) {
140 task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
143 task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
147 catch (IOException e) {
148 LOG.error("Can't find file " + file.getPath());
151 lesson.addTask(task);
153 final int lessonId = postLesson(project, lesson, indicator);
154 postUnit(project, lessonId, 1, sectionId);
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;
166 String requestBody = new Gson().toJson(unitWrapper);
167 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
180 catch (IOException e) {
181 LOG.error(e.getMessage());
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));
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);
207 final StepicWrappers.Section
208 postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
209 return postedSection.id;
211 catch (IOException e) {
212 LOG.error(e.getMessage());
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();
221 final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepicId()));
222 final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
223 registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
224 ApplicationManager.getApplication().invokeLater(() -> {
225 task.addTestsTexts("tests.py", task.getTestsText(project));
226 final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
227 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
230 final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
231 final CloseableHttpResponse response = client.execute(request);
232 final HttpEntity responseEntity = response.getEntity();
233 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
234 EntityUtils.consume(responseEntity);
235 final StatusLine line = response.getStatusLine();
236 if (line.getStatusCode() != HttpStatus.SC_OK) {
237 LOG.error("Failed to push " + responseString);
240 catch (IOException e) {
241 LOG.error(e.getMessage());
247 public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
248 final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId()));
250 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
251 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
254 final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
255 final CloseableHttpResponse response = client.execute(request);
256 final HttpEntity responseEntity = response.getEntity();
257 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
258 final StatusLine line = response.getStatusLine();
259 EntityUtils.consume(responseEntity);
260 if (line.getStatusCode() != HttpStatus.SC_OK) {
261 LOG.error("Failed to push " + responseString);
264 final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
265 for (Integer step : postedLesson.steps) {
266 deleteTask(step, project);
269 for (Task task : lesson.getTaskList()) {
270 indicator.checkCanceled();
271 postTask(project, task, lesson.getId());
273 return lesson.getId();
275 catch (IOException e) {
276 LOG.error(e.getMessage());
281 public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) {
282 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons");
284 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
285 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
288 final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
289 final CloseableHttpResponse response = client.execute(request);
290 final HttpEntity responseEntity = response.getEntity();
291 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
292 final StatusLine line = response.getStatusLine();
293 EntityUtils.consume(responseEntity);
294 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
295 LOG.error("Failed to push " + responseString);
298 final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
299 lesson.setId(postedLesson.getId());
300 for (Task task : lesson.getTaskList()) {
301 indicator.checkCanceled();
302 postTask(project, task, postedLesson.getId());
304 return postedLesson.getId();
306 catch (IOException e) {
307 LOG.error(e.getMessage());
312 public static void deleteTask(@NotNull final Integer task, Project project) {
313 final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task);
314 ApplicationManager.getApplication().invokeLater(() -> {
316 final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
317 final CloseableHttpResponse response = client.execute(request);
318 final HttpEntity responseEntity = response.getEntity();
319 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
320 EntityUtils.consume(responseEntity);
321 final StatusLine line = response.getStatusLine();
322 if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
323 LOG.error("Failed to delete task " + responseString);
326 catch (IOException e) {
327 LOG.error(e.getMessage());
332 public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
333 final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources");
334 //TODO: register type adapter for task files here?
335 final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().
336 registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create();
337 ApplicationManager.getApplication().invokeLater(() -> {
338 final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
339 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
342 final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(project);
343 final CloseableHttpResponse response = client.execute(request);
344 final StatusLine line = response.getStatusLine();
345 final HttpEntity responseEntity = response.getEntity();
346 final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
347 EntityUtils.consume(responseEntity);
348 if (line.getStatusCode() != HttpStatus.SC_CREATED) {
349 LOG.error("Failed to push " + responseString);
353 final JsonObject postedTask = new Gson().fromJson(responseString, JsonObject.class);
354 final JsonObject stepSource = postedTask.getAsJsonArray("step-sources").get(0).getAsJsonObject();
355 task.setStepicId(stepSource.getAsJsonPrimitive("id").getAsInt());
357 catch (IOException e) {
358 LOG.error(e.getMessage());