diff options
author | Tor Norbye <tnorbye@google.com> | 2014-08-20 17:01:23 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2014-08-20 17:01:23 -0700 |
commit | 1aa2e09bdbd413eacb677e9fa4b50630530d0656 (patch) | |
tree | 2f4cc6d69645bd460aa253fdecb606d764fbd25d /python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java | |
parent | 02cf98d65c798d368fcec43ed64a001d513bdd4f (diff) | |
download | idea-1aa2e09bdbd413eacb677e9fa4b50630530d0656.tar.gz |
Snapshot idea/138.1696 from git://git.jetbrains.org/idea/community.git
Change-Id: I50c97b83a815ce635e49a38380ba5b8765e4b16a
Diffstat (limited to 'python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java')
-rw-r--r-- | python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java new file mode 100644 index 000000000000..d4831d93e367 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java @@ -0,0 +1,393 @@ +package com.jetbrains.python.edu; + +import com.google.gson.*; +import com.google.gson.stream.JsonReader; +import com.intellij.facet.ui.FacetEditorValidator; +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.lang.javascript.boilerplate.GithubDownloadUtil; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.platform.templates.github.GeneratorException; +import com.intellij.platform.templates.github.ZipUtil; +import com.jetbrains.python.edu.course.Course; +import com.jetbrains.python.edu.course.CourseInfo; +import com.jetbrains.python.edu.ui.StudyNewProjectPanel; +import com.jetbrains.python.newProject.PythonProjectGenerator; +import icons.StudyIcons; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class StudyDirectoryProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator { + private static final Logger LOG = Logger.getInstance(StudyDirectoryProjectGenerator.class.getName()); + private static final String REPO_URL = "https://github.com/JetBrains/pycharm-courses/archive/master.zip"; + private static final String USER_NAME = "PyCharm"; + private static final String COURSE_META_FILE = "course.json"; + private static final String COURSE_NAME_ATTRIBUTE = "name"; + private static final Pattern CACHE_PATTERN = Pattern.compile("(name=(.*)) (path=(.*course.json)) (author=(.*)) (description=(.*))"); + private static final String REPOSITORY_NAME = "pycharm-courses"; + public static final String AUTHOR_ATTRIBUTE = "author"; + private final File myCoursesDir = new File(PathManager.getConfigPath(), "courses"); + private static final String CACHE_NAME = "courseNames.txt"; + private Map<CourseInfo, File> myCourses = new HashMap<CourseInfo, File>(); + private File mySelectedCourseFile; + private Project myProject; + public ValidationResult myValidationResult = new ValidationResult("selected course is not valid"); + + @Nls + @NotNull + @Override + public String getName() { + return "Study project"; + } + + + public void setCourses(Map<CourseInfo, File> courses) { + myCourses = courses; + } + + /** + * Finds selected course in courses by name. + * + * @param courseName name of selected course + */ + public void setSelectedCourse(@NotNull CourseInfo courseName) { + File courseFile = myCourses.get(courseName); + if (courseFile == null) { + LOG.error("invalid course in list"); + } + mySelectedCourseFile = courseFile; + } + + /** + * Adds course to courses specified in params + * + * @param courseDir must be directory containing course file + * @return added course name or null if course is invalid + */ + @Nullable + private static CourseInfo addCourse(Map<CourseInfo, File> courses, File courseDir) { + if (courseDir.isDirectory()) { + File[] courseFiles = courseDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.equals(COURSE_META_FILE); + } + }); + if (courseFiles.length != 1) { + LOG.info("User tried to add course with more than one or without course files"); + return null; + } + File courseFile = courseFiles[0]; + CourseInfo courseInfo = getCourseInfo(courseFile); + if (courseInfo != null) { + courses.put(courseInfo, courseFile); + } + return courseInfo; + } + return null; + } + + + /** + * Adds course from zip archive to courses + * + * @return added course name or null if course is invalid + */ + @Nullable + public CourseInfo addLocalCourse(String zipFilePath) { + File file = new File(zipFilePath); + try { + String fileName = file.getName(); + String unzippedName = fileName.substring(0, fileName.indexOf(".")); + File courseDir = new File(myCoursesDir, unzippedName); + ZipUtil.unzip(null, courseDir, file, null, null, true); + CourseInfo courseName = addCourse(myCourses, courseDir); + flushCache(); + return courseName; + } + catch (IOException e) { + LOG.error("Failed to unzip course archive"); + LOG.error(e); + } + return null; + } + + @Nullable + @Override + public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException { + return null; + } + + @Nullable + @Override + public Icon getLogo() { + return StudyIcons.Playground; + } + + + @Override + public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, + @Nullable Object settings, @NotNull Module module) { + + myProject = project; + Reader reader = null; + try { + reader = new InputStreamReader(new FileInputStream(mySelectedCourseFile)); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + Course course = gson.fromJson(reader, Course.class); + course.init(false); + course.create(baseDir, new File(mySelectedCourseFile.getParent())); + course.setResourcePath(mySelectedCourseFile.getAbsolutePath()); + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + StudyTaskManager.getInstance(project).setCourse(course); + } + catch (FileNotFoundException e) { + LOG.error(e); + } + finally { + StudyUtils.closeSilently(reader); + } + } + + /** + * Downloads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#REPO_URL} + * and unzips them into {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir} + */ + + public void downloadAndUnzip(boolean needProgressBar) { + File outputFile = new File(PathManager.getConfigPath(), "courses.zip"); + try { + if (!needProgressBar) { + GithubDownloadUtil.downloadAtomically(null, REPO_URL, + outputFile, USER_NAME, REPOSITORY_NAME); + } + else { + GithubDownloadUtil.downloadContentToFileWithProgressSynchronously(myProject, REPO_URL, "downloading courses", outputFile, USER_NAME, + REPOSITORY_NAME, false); + } + if (outputFile.exists()) { + ZipUtil.unzip(null, myCoursesDir, outputFile, null, null, true); + if (!outputFile.delete()) { + LOG.error("Failed to delete", outputFile.getName()); + } + File[] files = myCoursesDir.listFiles(); + if (files != null) { + for (File file : files) { + String fileName = file.getName(); + if (StudyUtils.isZip(fileName)) { + ZipUtil.unzip(null, new File(myCoursesDir, fileName.substring(0, fileName.indexOf("."))), file, null, null, true); + if (!file.delete()) { + LOG.error("Failed to delete", fileName); + } + } + } + } + } else { + LOG.debug("failed to download course"); + } + } + catch (IOException e) { + LOG.error(e); + } + catch (GeneratorException e) { + LOG.error(e); + } + } + + public Map<CourseInfo, File> getLoadedCourses() { + return myCourses; + } + + /** + * Parses courses located in {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir} + * to {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCourses} + * + * @return map with course names and course files location + */ + public Map<CourseInfo, File> loadCourses() { + Map<CourseInfo, File> courses = new HashMap<CourseInfo, File>(); + if (myCoursesDir.exists()) { + File[] courseDirs = myCoursesDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + for (File courseDir : courseDirs) { + addCourse(courses, courseDir); + } + } + return courses; + } + + /** + * Parses course json meta file and finds course name + * + * @return information about course or null if course file is invalid + */ + @Nullable + private static CourseInfo getCourseInfo(File courseFile) { + CourseInfo courseInfo = null; + BufferedReader reader = null; + try { + if (courseFile.getName().equals(COURSE_META_FILE)) { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(courseFile))); + JsonReader r = new JsonReader(reader); + JsonParser parser = new JsonParser(); + JsonElement el = parser.parse(r); + String courseName = el.getAsJsonObject().get(COURSE_NAME_ATTRIBUTE).getAsString(); + String courseAuthor = el.getAsJsonObject().get(AUTHOR_ATTRIBUTE).getAsString(); + String courseDescription = el.getAsJsonObject().get("description").getAsString(); + courseInfo = new CourseInfo(courseName, courseAuthor, courseDescription); + } + } + catch (Exception e) { + //error will be shown in UI + } + finally { + StudyUtils.closeSilently(reader); + } + return courseInfo; + } + + @NotNull + @Override + public ValidationResult validate(@NotNull String s) { + return myValidationResult; + } + + public void setValidationResult(ValidationResult validationResult) { + myValidationResult = validationResult; + } + + /** + * @return courses from memory or from cash file or parses course directory + */ + public Map<CourseInfo, File> getCourses() { + if (!myCourses.isEmpty()) { + return myCourses; + } + if (myCoursesDir.exists()) { + File cacheFile = new File(myCoursesDir, CACHE_NAME); + if (cacheFile.exists()) { + myCourses = getCoursesFromCache(cacheFile); + if (!myCourses.isEmpty()) { + return myCourses; + } + } + myCourses = loadCourses(); + if (!myCourses.isEmpty()) { + return myCourses; + } + } + downloadAndUnzip(false); + myCourses = loadCourses(); + flushCache(); + return myCourses; + } + + /** + * Writes courses to cash file {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME} + */ + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + public void flushCache() { + File cashFile = new File(myCoursesDir, CACHE_NAME); + PrintWriter writer = null; + try { + if (!cashFile.exists()) { + final boolean created = cashFile.createNewFile(); + if (!created) { + LOG.error("Cannot flush courses cache. Can't create " + CACHE_NAME + " file"); + return; + } + } + writer = new PrintWriter(cashFile); + for (Map.Entry<CourseInfo, File> course : myCourses.entrySet()) { + CourseInfo courseInfo = course.getKey(); + String line = String + .format("name=%s path=%s author=%s description=%s", courseInfo.getName(), course.getValue(), courseInfo.getAuthor(), + courseInfo.getDescription()); + writer.println(line); + } + } + catch (FileNotFoundException e) { + LOG.error(e); + } + catch (IOException e) { + LOG.error(e); + } + finally { + StudyUtils.closeSilently(writer); + } + } + + /** + * Loads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME} file + * + * @return map of course names and course files + */ + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + private static Map<CourseInfo, File> getCoursesFromCache(File cashFile) { + Map<CourseInfo, File> coursesFromCash = new HashMap<CourseInfo, File>(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(cashFile))); + String line; + + while ((line = reader.readLine()) != null) { + Matcher matcher = CACHE_PATTERN.matcher(line); + if (matcher.matches()) { + String courseName = matcher.group(2); + File file = new File(matcher.group(4)); + String author = matcher.group(6); + String description = matcher.group(8); + CourseInfo courseInfo = new CourseInfo(courseName, author, description); + if (file.exists()) { + coursesFromCash.put(courseInfo, file); + } + } + } + } + catch (FileNotFoundException e) { + LOG.error(e); + } + catch (IOException e) { + LOG.error(e); + } + finally { + StudyUtils.closeSilently(reader); + } + return coursesFromCash; + } + + @Nullable + @Override + public JPanel extendBasePanel() throws ProcessCanceledException { + StudyNewProjectPanel settingsPanel = new StudyNewProjectPanel(this); + settingsPanel.registerValidators(new FacetValidatorsManager() { + public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) { + throw new UnsupportedOperationException(); + } + public void validate() { + fireStateChanged(); + } + }); + return settingsPanel.getContentPanel(); + } +} |