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.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;
34 import java.io.IOException;
35 import java.util.Collections;
36 import java.util.List;
38 public class CCStepicConnector {
39 private static final Logger LOG = Logger.getInstance(CCStepicConnector.class.getName());
41 private CCStepicConnector() {
44 public static CourseInfo getCourseInfo(Project project, String courseId) {
45 final String url = EduStepicNames.COURSES + "/" + courseId;
47 final StepicWrappers.CoursesContainer coursesContainer =
48 EduStepicAuthorizedClient.getFromStepic(url, StepicWrappers.CoursesContainer.class, project);
49 return coursesContainer.courses.get(0);
51 catch (IOException e) {
52 LOG.error(e.getMessage());
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) {
60 public void run(@NotNull final ProgressIndicator indicator) {
61 postCourse(project, course, indicator);
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");
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());
80 course.setAuthors(Collections.singletonList(currentUser));
83 String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course));
84 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
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()));
101 for (Lesson lesson : course.getLessons()) {
102 indicator.checkCanceled();
103 final int lessonId = postLesson(project, lesson, indicator);
104 postUnit(project, lessonId, position, sectionId);
107 ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(project, postedCourse.id, indicator));
109 catch (IOException e) {
110 LOG.error(e.getMessage());
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() {
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(".");
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);
133 task.setText(EduNames.PYCHARM_ADDITIONAL);
134 for (VirtualFile file : files) {
137 if (EduUtils.isImage(file.getName())) {
138 task.addTestsTexts(file.getName(), Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())));
141 task.addTestsTexts(file.getName(), FileUtil.loadTextAndClose(file.getInputStream()));
145 catch (IOException e) {
146 LOG.error("Can't find file " + file.getPath());
149 lesson.addTask(task);
151 final int lessonId = postLesson(project, lesson, indicator);
152 postUnit(project, lessonId, 1, sectionId);
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;
164 String requestBody = new Gson().toJson(unitWrapper);
165 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
178 catch (IOException e) {
179 LOG.error(e.getMessage());
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));
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);
205 final StepicWrappers.Section
206 postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0);
207 return postedSection.id;
209 catch (IOException e) {
210 LOG.error(e.getMessage());
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();
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));
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);
237 catch (IOException e) {
238 LOG.error(e.getMessage());
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()));
247 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
248 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
261 final Lesson postedLesson = new Gson().fromJson(responseString, Course.class).getLessons().get(0);
262 for (Integer step : postedLesson.steps) {
263 deleteTask(step, project);
266 for (Task task : lesson.getTaskList()) {
267 indicator.checkCanceled();
268 postTask(project, task, lesson.getId());
270 return lesson.getId();
272 catch (IOException e) {
273 LOG.error(e.getMessage());
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");
281 String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson));
282 request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
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);
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());
301 return postedLesson.getId();
303 catch (IOException e) {
304 LOG.error(e.getMessage());
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(() -> {
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);
323 catch (IOException e) {
324 LOG.error(e.getMessage());
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));
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);
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());
352 catch (IOException e) {
353 LOG.error(e.getMessage());