/** * Copyright [2015] [Christian Loehnert] * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.ks.blogging.grav.pages; import com.google.common.base.Charsets; import com.google.common.util.concurrent.ThreadFactoryBuilder; import de.ks.blogging.grav.entity.GravBlog; import de.ks.blogging.grav.posts.BasePost; import de.ks.blogging.grav.posts.BlogItem; import de.ks.blogging.grav.posts.Page; import de.ks.blogging.grav.posts.PostParser; import org.apache.commons.lang3.StringUtils; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Status; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.revwalk.RevCommit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.inject.Default; import javax.inject.Singleton; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @Singleton @Default public class GravPages implements AutoCloseable { private static final Logger log = LoggerFactory.getLogger(GravPages.class); protected GravBlog blog; protected final Set<BasePost> posts = new HashSet<>(); protected final Set<Future<BasePost>> postLoader = new HashSet<>(); protected final ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).build()); protected final AtomicReference<Git> git = new AtomicReference<>(); public GravPages() { this(null); } public GravPages(GravBlog blog) { this.blog = blog; } public GravBlog getBlog() { return blog; } public void setBlog(GravBlog blog) { this.blog = blog; } public synchronized GravPages scan() { String filePath = blog.getPagesDirectory(); posts.clear(); postLoader.clear(); SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file != null && file.toString().endsWith("md")) { postLoader.add(executorService.submit(new PostParser(file, blog))); } return FileVisitResult.CONTINUE; } }; try { Files.walkFileTree(Paths.get(filePath), visitor); } catch (IOException e) { log.error("Could not walk fileTree {}", filePath, e); } return this; } public synchronized Set<BasePost> getPosts() { if (!postLoader.isEmpty()) { postLoader.forEach(f -> { try { BasePost loaded = f.get(); posts.add(loaded); } catch (Exception e) { log.error("Could not read post ", e); } }); postLoader.clear(); } return posts; } public synchronized BasePost addPost(String title, String fileName) { File file = getPostFile(title, fileName); BasePost page = new BasePost(file, blog.getDateFormat()); page.getHeader().setTitle(title); page.getHeader().setAuthor(blog.getDefaultAuthor()); return page; } public synchronized Page addPage(String title, int index) { String fileName = "default.md"; File file = getPostFile(index + "." + title, fileName); Page page = new Page(file, blog.getDateFormat()); page.getHeader().setTitle(title); page.getHeader().setAuthor(blog.getDefaultAuthor()); return page; } public synchronized BlogItem addBlogItem(String title) { String fileName = "item.md"; String blogSubPath = blog.getBlogSubPath(); File parent = new File(new File(blog.getPagesDirectory()), blogSubPath); File file = getPostFile(title, fileName, parent); BlogItem page = new BlogItem(file, blog.getDateFormat()); page.getHeader().setTitle(title); page.getHeader().setCategory("blog"); page.getHeader().setAuthor(blog.getDefaultAuthor()); return page; } protected File getPostFile(String title, String fileName) { return getPostFile(title, fileName, new File(blog.getPagesDirectory())); } protected File getPostFile(String title, String fileName, File parent) { StringBuilder folderName = new StringBuilder(); char[] chars = StringUtils.replace(title.toLowerCase(Locale.ROOT).trim(), " ", "-").toCharArray(); Charset ascii = Charsets.US_ASCII; CharsetEncoder encoder = ascii.newEncoder(); for (char character : chars) { boolean isCharacter = Character.isLetterOrDigit(character) || character == '-' || character == '.'; boolean isAscii = encoder.canEncode(character); if (isCharacter && isAscii) { folderName.append(character); } else if (character == '\u00e4') { folderName.append("ae"); } else if (character == '\u00f6') { folderName.append("oe"); } else if (character == '\u00fc') { folderName.append("ue"); } } File folder = new File(parent, folderName.toString()); return new File(folder, fileName); } public Collection<Page> getPages() { return getPosts().stream().filter(p -> (p instanceof Page)).map(p -> (Page) p).collect(Collectors.toList()); } public Collection<BlogItem> getBlogItems() { return getPosts().stream().filter(p -> (p instanceof BlogItem)).map(p -> (BlogItem) p).collect(Collectors.toList()); } public Collection<BasePost> getAllPosts() { return getPosts(); } public boolean hasGitRepository() { return new RepositoryBuilder().findGitDir(new File(blog.getPagesDirectory())).setMustExist(true).getGitDir() != null; } public Git getGit() { git.getAndUpdate(git -> { if (git == null) { try { Repository repository = new RepositoryBuilder().findGitDir(new File(blog.getPagesDirectory())).setMustExist(true).build(); return new Git(repository); } catch (Exception e) { log.error("Could not get Git ", e); return null; } } else { return git; } }); return git.get(); } public void addCommit(String msg) throws RuntimeException { Git git = getGit(); if (git != null) { try { Status status = git.status().call(); Set<String> modified = status.getModified(); Set<String> untracked = status.getUntracked(); AddCommand add = git.add(); modified.forEach(s -> add.addFilepattern(s)); untracked.forEach(s -> add.addFilepattern(s)); add.call(); CommitCommand commit = git.commit(); if (msg == null || msg.isEmpty()) { commit.setAmend(true); } else { commit.setMessage(msg); } RevCommit rev = commit.call(); log.info("Commited change {} with new rev {}", msg, rev); } catch (Exception e) { log.error("Could not add and commit ", e); throw new RuntimeException(e); } } } @Override public void close() throws Exception { if (git.get() != null) { git.get().close(); } } }