/*
documentr - Edit, maintain, and present software documentation on the web.
Copyright (C) 2012-2013 Maik Schreiber
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.blizzy.documentr.page;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.RebaseCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.gitective.core.BlobUtils;
import org.gitective.core.CommitFinder;
import org.gitective.core.CommitUtils;
import org.gitective.core.PathFilterUtils;
import org.gitective.core.filter.commit.AndCommitFilter;
import org.gitective.core.filter.commit.CommitFilter;
import org.gitective.core.filter.commit.CommitLimitFilter;
import org.gitective.core.filter.commit.CommitListFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import de.blizzy.documentr.DocumentrConstants;
import de.blizzy.documentr.access.User;
import de.blizzy.documentr.repository.IGlobalRepositoryManager;
import de.blizzy.documentr.repository.ILockedRepository;
import de.blizzy.documentr.repository.RepositoryUtil;
import de.blizzy.documentr.util.Util;
@Component
@Slf4j
class PageStore implements IPageStore {
private static final String PARENT_PAGE_PATH = "parentPagePath"; //$NON-NLS-1$
private static final String TITLE = "title"; //$NON-NLS-1$
private static final String CONTENT_TYPE = "contentType"; //$NON-NLS-1$
private static final String PAGE_DATA = "pageData"; //$NON-NLS-1$
private static final String VERSION_LATEST = "latest"; //$NON-NLS-1$
private static final String VERSION_PREVIOUS = "previous"; //$NON-NLS-1$
private static final String TAGS = "tags"; //$NON-NLS-1$
private static final String VIEW_RESTRICTION_ROLE = "viewRestrictionRole"; //$NON-NLS-1$
private static final String ORDER_INDEX = "orderIndex"; //$NON-NLS-1$
@Autowired
private IGlobalRepositoryManager globalRepositoryManager;
@Autowired
private EventBus eventBus;
@Override
public MergeConflict savePage(String projectName, String branchName, String path, Page page, String baseCommit,
User user) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
Assert.notNull(user);
try {
MergeConflict conflict = savePageInternal(projectName, branchName, path, DocumentrConstants.PAGE_SUFFIX, page,
baseCommit, DocumentrConstants.PAGES_DIR_NAME, user);
if (conflict == null) {
eventBus.post(new PageChangedEvent(projectName, branchName, path));
}
return conflict;
} catch (GitAPIException e) {
throw new IOException(e);
}
}
@Override
public void saveAttachment(String projectName, String branchName, String pagePath, String name,
Page attachment, User user) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(pagePath);
Assert.hasLength(name);
Assert.notNull(attachment);
Assert.notNull(user);
// check if page exists by trying to load it
getPage(projectName, branchName, pagePath, false);
try {
savePageInternal(projectName, branchName, pagePath + "/" + name, DocumentrConstants.PAGE_SUFFIX, attachment, //$NON-NLS-1$
null, DocumentrConstants.ATTACHMENTS_DIR_NAME, user);
} catch (GitAPIException e) {
throw new IOException(e);
}
}
private MergeConflict savePageInternal(String projectName, String branchName, String path, String suffix, Page page,
String baseCommit, String rootDir, User user) throws IOException, GitAPIException {
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
return savePageInternal(projectName, branchName, path, suffix, page, baseCommit, rootDir, user, repo, true);
} finally {
Util.closeQuietly(repo);
}
}
private MergeConflict savePageInternal(String projectName, String branchName, String path, String suffix, Page page,
String baseCommit, String rootDir, User user, ILockedRepository repo, boolean push)
throws IOException, GitAPIException {
Git git = Git.wrap(repo.r());
String headCommit = CommitUtils.getHead(repo.r()).getName();
if ((baseCommit != null) && headCommit.equals(baseCommit)) {
baseCommit = null;
}
String editBranchName = "_edit_" + String.valueOf((long) (Math.random() * Long.MAX_VALUE)); //$NON-NLS-1$
if (baseCommit != null) {
git.branchCreate()
.setName(editBranchName)
.setStartPoint(baseCommit)
.call();
git.checkout()
.setName(editBranchName)
.call();
}
Map<String, Object> metaMap = new HashMap<String, Object>();
metaMap.put(TITLE, page.getTitle());
metaMap.put(CONTENT_TYPE, page.getContentType());
if (!page.getTags().isEmpty()) {
metaMap.put(TAGS, page.getTags());
}
metaMap.put(VIEW_RESTRICTION_ROLE, page.getViewRestrictionRole());
if (page.getOrderIndex() >= 0) {
metaMap.put(ORDER_INDEX, page.getOrderIndex());
}
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
String json = gson.toJson(metaMap);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = new File(workingDir, rootDir);
File workingFile = Util.toFile(pagesDir, path + DocumentrConstants.META_SUFFIX);
FileUtils.write(workingFile, json, Charsets.UTF_8);
PageData pageData = page.getData();
if (pageData != null) {
workingFile = Util.toFile(pagesDir, path + suffix);
FileUtils.writeByteArrayToFile(workingFile, pageData.getData());
}
AddCommand addCommand = git.add()
.addFilepattern(rootDir + "/" + path + DocumentrConstants.META_SUFFIX); //$NON-NLS-1$
if (pageData != null) {
addCommand.addFilepattern(rootDir + "/" + path + suffix); //$NON-NLS-1$
}
addCommand.call();
PersonIdent ident = new PersonIdent(user.getLoginName(), user.getEmail());
git.commit()
.setAuthor(ident)
.setCommitter(ident)
.setMessage(rootDir + "/" + path + suffix).call(); //$NON-NLS-1$
MergeConflict conflict = null;
if (baseCommit != null) {
git.rebase()
.setUpstream(branchName)
.call();
if (repo.r().getRepositoryState() != RepositoryState.SAFE) {
String text = FileUtils.readFileToString(workingFile, Charsets.UTF_8);
conflict = new MergeConflict(text, headCommit);
git.rebase()
.setOperation(RebaseCommand.Operation.ABORT)
.call();
}
git.checkout()
.setName(branchName)
.call();
if (conflict == null) {
git.merge()
.include(repo.r().resolve(editBranchName))
.call();
}
git.branchDelete()
.setBranchNames(editBranchName)
.setForce(true)
.call();
}
if (push && (conflict == null)) {
Stopwatch stopwatch = new Stopwatch().start();
git.push().call();
log.trace("push took {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS)); //$NON-NLS-1$
}
page.setParentPagePath(getParentPagePath(path, repo.r()));
page.setPath(path);
if (conflict == null) {
PageUtil.updateProjectEditTime(projectName);
}
return conflict;
}
@Override
public Page getPage(String projectName, String branchName, String path, boolean loadData) throws IOException {
return getPage(projectName, branchName, path, null, loadData);
}
@Override
public Page getPage(String projectName, String branchName, String path, String commit, boolean loadData) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
if (commit != null) {
Assert.hasLength(commit);
}
if (log.isDebugEnabled()) {
log.debug("loading page {}/{}/{}, commit: {}, loadData: {}", //$NON-NLS-1$
projectName, branchName, Util.toUrlPagePath(path), commit, Boolean.valueOf(loadData));
}
try {
Map<String, Object> pageMap = getPageData(projectName, branchName, path, DocumentrConstants.PAGES_DIR_NAME,
commit, loadData);
String parentPagePath = (String) pageMap.get(PARENT_PAGE_PATH);
String title = (String) pageMap.get(TITLE);
String contentType = (String) pageMap.get(CONTENT_TYPE);
@SuppressWarnings("unchecked")
List<String> tagsList = (List<String>) pageMap.get(TAGS);
if (tagsList == null) {
tagsList = Collections.emptyList();
}
Set<String> tags = Sets.newHashSet(tagsList);
String viewRestrictionRole = (String) pageMap.get(VIEW_RESTRICTION_ROLE);
Double orderIndexDouble = (Double) pageMap.get(ORDER_INDEX);
int orderIndex = (orderIndexDouble != null) ? orderIndexDouble.intValue() : DocumentrConstants.PAGE_ORDER_INDEX_UNORDERED;
PageData pageData = (PageData) pageMap.get(PAGE_DATA);
Page page = new Page(title, contentType, pageData);
page.setParentPagePath(parentPagePath);
page.setPath(path);
page.setTags(tags);
page.setViewRestrictionRole(viewRestrictionRole);
page.setOrderIndex(orderIndex);
return page;
} catch (GitAPIException e) {
throw new IOException(e);
}
}
private Map<String, Object> getPageData(String projectName, String branchName, String path, String rootDir,
String commit, boolean loadData) throws IOException, GitAPIException {
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = new File(workingDir, rootDir);
File workingFile = Util.toFile(pagesDir, path + DocumentrConstants.META_SUFFIX);
if (!workingFile.isFile()) {
throw new PageNotFoundException(projectName, branchName, path);
}
String json;
if (commit != null) {
json = BlobUtils.getContent(repo.r(), commit, DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.META_SUFFIX); //$NON-NLS-1$
} else {
json = FileUtils.readFileToString(workingFile, Charsets.UTF_8);
}
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
Map<String, Object> pageMap = gson.fromJson(json, new TypeToken<Map<String, Object>>(){}.getType());
if (loadData) {
workingFile = Util.toFile(pagesDir, path + DocumentrConstants.PAGE_SUFFIX);
byte[] data;
if (commit != null) {
data = BlobUtils.getRawContent(repo.r(), commit, DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$
} else {
data = FileUtils.readFileToByteArray(workingFile);
}
String contentType = (String) pageMap.get(CONTENT_TYPE);
PageData pageData;
if (contentType.equals(PageTextData.CONTENT_TYPE)) {
pageData = PageTextData.fromBytes(data);
} else {
pageData = new PageData(data, contentType);
}
pageMap.put(PAGE_DATA, pageData);
}
String parentPagePath = getParentPagePath(path, repo.r());
if (parentPagePath != null) {
pageMap.put(PARENT_PAGE_PATH, parentPagePath);
}
return pageMap;
} finally {
Util.closeQuietly(repo);
}
}
private String getParentPagePath(String path, Repository repo) {
File workingDir = RepositoryUtil.getWorkingDir(repo);
File pagesDir = new File(workingDir, DocumentrConstants.PAGES_DIR_NAME);
File pageFile = Util.toFile(pagesDir, path + DocumentrConstants.PAGE_SUFFIX);
File dir = pageFile.getParentFile();
StringBuilder buf = new StringBuilder();
while (!dir.equals(pagesDir)) {
if (buf.length() > 0) {
buf.insert(0, '/');
}
buf.insert(0, dir.getName());
dir = dir.getParentFile();
}
return (buf.length() > 0) ? buf.toString() : null;
}
@Override
public Page getAttachment(String projectName, String branchName, String pagePath, String name)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(pagePath);
Assert.hasLength(name);
try {
Map<String, Object> pageMap = getPageData(projectName, branchName, pagePath + "/" + name, //$NON-NLS-1$
DocumentrConstants.ATTACHMENTS_DIR_NAME, null, true);
String parentPagePath = (String) pageMap.get(PARENT_PAGE_PATH);
String contentType = (String) pageMap.get(CONTENT_TYPE);
PageData pageData = (PageData) pageMap.get(PAGE_DATA);
Page page = new Page(null, contentType, pageData);
page.setParentPagePath(parentPagePath);
page.setPath(pagePath);
return page;
} catch (GitAPIException e) {
throw new IOException(e);
}
}
@Override
public List<String> listPageAttachments(String projectName, String branchName, String pagePath)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(pagePath);
// check if page exists by trying to load it
getPage(projectName, branchName, pagePath, false);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File attachmentsDir = new File(workingDir, DocumentrConstants.ATTACHMENTS_DIR_NAME);
File pageAttachmentsDir = Util.toFile(attachmentsDir, pagePath);
List<String> names = Collections.emptyList();
if (pageAttachmentsDir.isDirectory()) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(DocumentrConstants.META_SUFFIX);
}
};
List<File> files = Lists.newArrayList(pageAttachmentsDir.listFiles(filter));
Function<File, String> function = new Function<File, String>() {
@Override
public String apply(File file) {
return StringUtils.substringBeforeLast(file.getName(), DocumentrConstants.META_SUFFIX);
}
};
names = Lists.newArrayList(Lists.transform(files, function));
Collections.sort(names);
}
return names;
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
@Override
public List<String> listAllPagePaths(String projectName, String branchName) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = new File(workingDir, DocumentrConstants.PAGES_DIR_NAME);
return listPagePaths(pagesDir, true);
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
private List<String> listPagePaths(File pagesDir, boolean recursive) {
List<File> paths = listPageFilesInDir(pagesDir, recursive);
String prefix = pagesDir.getAbsolutePath() + File.separator;
final int prefixLen = prefix.length();
final int pageSuffixLen = DocumentrConstants.PAGE_SUFFIX.length();
Function<File, String> function = new Function<File, String>() {
@Override
public String apply(File file) {
String path = file.getAbsolutePath();
path = path.substring(prefixLen, path.length() - pageSuffixLen);
path = path.replace('\\', '/');
return path;
}
};
List<String> pagePaths = Lists.newArrayList(Lists.transform(paths, function));
Collections.sort(pagePaths);
return pagePaths;
}
private List<File> listPageFilesInDir(File dir, boolean recursive) {
List<File> result = Lists.newArrayList();
if (dir.isDirectory()) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return (pathname.isFile() && pathname.getName().endsWith(DocumentrConstants.PAGE_SUFFIX)) ||
pathname.isDirectory();
}
};
File[] files = dir.listFiles(filter);
for (File file : files) {
if (file.isDirectory()) {
if (recursive) {
result.addAll(listPageFilesInDir(file, true));
}
} else {
result.add(file);
}
}
}
return result;
}
@Override
public boolean isPageSharedWithOtherBranches(String projectName, String branchName, String path) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
List<String> branches = getBranchesPageIsSharedWith(projectName, branchName, path);
return branches.size() >= 2;
}
@Override
public List<String> getBranchesPageIsSharedWith(String projectName, String branchName, String path)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
List<String> allBranches = globalRepositoryManager.listProjectBranches(projectName);
ILockedRepository centralRepo = null;
Set<String> branchesWithCommit = Collections.emptySet();
try {
centralRepo = globalRepositoryManager.getProjectCentralRepository(projectName);
String repoPath = DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX; //$NON-NLS-1$
RevCommit commit = CommitUtils.getLastCommit(centralRepo.r(), branchName, repoPath);
if (commit != null) {
// get all branches where this commit is in their history
branchesWithCommit = getBranchesWithCommit(commit, allBranches, centralRepo.r());
if (branchesWithCommit.size() >= 2) {
// remove all branches where the previous commit is no longer visible
// due to newer commits on those branches
for (Iterator<String> iter = branchesWithCommit.iterator(); iter.hasNext();) {
String branch = iter.next();
RevCommit c = CommitUtils.getLastCommit(centralRepo.r(), branch, repoPath);
if (!c.equals(commit)) {
iter.remove();
}
}
}
}
} finally {
Util.closeQuietly(centralRepo);
}
List<String> branches = Lists.newArrayList(branchesWithCommit);
if (!branches.contains(branchName)) {
branches.add(branchName);
}
Collections.sort(branches);
return branches;
}
private Set<String> getBranchesWithCommit(final RevCommit commit, List<String> allBranches, Repository centralRepo) {
final Set<String> result = Sets.newHashSet();
for (final String branch : allBranches) {
CommitFilter matcher = new CommitFilter() {
@Override
public boolean include(RevWalk revWalk, RevCommit revCommit) {
if (revCommit.equals(commit)) {
result.add(branch);
throw StopWalkException.INSTANCE;
}
return true;
}
};
CommitFinder finder = new CommitFinder(centralRepo);
finder.setMatcher(matcher);
finder.findFrom(branch);
}
return result;
}
@Override
public List<String> listChildPagePaths(String projectName, String branchName, final String path) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = Util.toFile(new File(workingDir, DocumentrConstants.PAGES_DIR_NAME), path);
List<String> paths = Lists.newArrayList(listPagePaths(pagesDir, false));
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String childName) {
return path + "/" + childName; //$NON-NLS-1$
}
};
paths = Lists.transform(paths, function);
return paths;
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
@Override
public List<Page> listChildPagesOrdered(final String projectName, final String branchName, final String path,
final Locale locale) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = Util.toFile(new File(workingDir, DocumentrConstants.PAGES_DIR_NAME), path);
List<String> paths = Lists.newArrayList(listPagePaths(pagesDir, false));
Function<String, String> pathFunction = new Function<String, String>() {
@Override
public String apply(String childName) {
return path + "/" + childName; //$NON-NLS-1$
}
};
paths = Lists.transform(paths, pathFunction);
Function<String, Page> pageFunction = new Function<String, Page>() {
@Override
public Page apply(String childPath) {
try {
return getPage(projectName, branchName, childPath, false);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
List<Page> pages = Lists.newArrayList(Lists.transform(paths, pageFunction));
Comparator<Page> comparator = new Comparator<Page>() {
@Override
public int compare(Page page1, Page page2) {
int idx1 = page1.getOrderIndex();
int idx2 = page2.getOrderIndex();
if ((idx1 >= 0) && (idx2 >= 0)) {
return Integer.valueOf(idx1).compareTo(idx2);
} else if ((idx1 < 0) && (idx2 < 0)) {
String title1 = page1.getTitle();
String title2 = page2.getTitle();
if (locale != null) {
return Collator.getInstance(locale).compare(title1, title2);
} else {
return title1.compareToIgnoreCase(title2);
}
} else if (idx1 >= 0) {
return -1;
} else {
return 1;
}
}
};
Collections.sort(pages, comparator);
return pages;
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
@Override
public void deletePage(String projectName, String branchName, final String path, User user) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
Assert.notNull(user);
ILockedRepository repo = null;
List<String> oldPagePaths;
boolean deleted = false;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = new File(workingDir, DocumentrConstants.PAGES_DIR_NAME);
File oldSubPagesDir = Util.toFile(pagesDir, path);
oldPagePaths = listPagePaths(oldSubPagesDir, true);
oldPagePaths = Lists.newArrayList(Lists.transform(oldPagePaths, new Function<String, String>() {
@Override
public String apply(String p) {
return path + "/" + p; //$NON-NLS-1$
}
}));
oldPagePaths.add(path);
Git git = Git.wrap(repo.r());
File file = Util.toFile(pagesDir, path + DocumentrConstants.PAGE_SUFFIX);
if (file.isFile()) {
git.rm()
.addFilepattern(DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX) //$NON-NLS-1$
.call();
deleted = true;
}
file = Util.toFile(pagesDir, path + DocumentrConstants.META_SUFFIX);
if (file.isFile()) {
git.rm()
.addFilepattern(DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.META_SUFFIX) //$NON-NLS-1$
.call();
deleted = true;
}
file = Util.toFile(pagesDir, path);
if (file.isDirectory()) {
git.rm()
.addFilepattern(DocumentrConstants.PAGES_DIR_NAME + "/" + path) //$NON-NLS-1$
.call();
deleted = true;
}
File attachmentsDir = new File(workingDir, DocumentrConstants.ATTACHMENTS_DIR_NAME);
file = Util.toFile(attachmentsDir, path);
if (file.isDirectory()) {
git.rm()
.addFilepattern(DocumentrConstants.ATTACHMENTS_DIR_NAME + "/" + path) //$NON-NLS-1$
.call();
deleted = true;
}
if (deleted) {
PersonIdent ident = new PersonIdent(user.getLoginName(), user.getEmail());
git.commit()
.setAuthor(ident)
.setCommitter(ident)
.setMessage("delete " + path) //$NON-NLS-1$
.call();
git.push().call();
PageUtil.updateProjectEditTime(projectName);
}
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
if (deleted) {
eventBus.post(new PagesDeletedEvent(projectName, branchName, Sets.newHashSet(oldPagePaths)));
}
}
@Override
public PageMetadata getPageMetadata(String projectName, String branchName, String path) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
return getPageMetadataInternal(projectName, branchName, path, DocumentrConstants.PAGES_DIR_NAME);
}
@Override
public PageMetadata getAttachmentMetadata(String projectName, String branchName, String path, String name)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
Assert.hasLength(name);
return getPageMetadataInternal(projectName, branchName, path + "/" + name, DocumentrConstants.ATTACHMENTS_DIR_NAME); //$NON-NLS-1$
}
private PageMetadata getPageMetadataInternal(String projectName, String branchName, String path, String rootDir)
throws IOException {
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
RevCommit metaCommit = CommitUtils.getLastCommit(repo.r(), rootDir + "/" + path + DocumentrConstants.META_SUFFIX); //$NON-NLS-1$
RevCommit pageCommit = CommitUtils.getLastCommit(repo.r(), rootDir + "/" + path + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$
if ((metaCommit == null) && (pageCommit == null)) {
throw new PageNotFoundException(projectName, branchName, path);
}
RevCommit commit = getNewestCommit(metaCommit, pageCommit);
PersonIdent committer = commit.getAuthorIdent();
String lastEditedBy = null;
if (committer != null) {
lastEditedBy = committer.getName();
}
// TODO: would love to use authored time
Date lastEdited = new Date(TimeUnit.MILLISECONDS.convert(commit.getCommitTime(), TimeUnit.SECONDS));
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File rootDirFile = new File(workingDir, rootDir);
File file = Util.toFile(rootDirFile, path + DocumentrConstants.PAGE_SUFFIX);
return new PageMetadata(lastEditedBy, lastEdited, file.length(), commit.getName());
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
private RevCommit getNewestCommit(RevCommit... commits) {
RevCommit newestCommit = null;
int newestCommitTime = Integer.MIN_VALUE;
for (RevCommit commit : commits) {
if (commit != null) {
int time = commit.getCommitTime();
if (time > newestCommitTime) {
newestCommit = commit;
newestCommitTime = time;
}
}
}
return newestCommit;
}
@Override
public void relocatePage(final String projectName, final String branchName, final String path, String newParentPagePath,
User user) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
Assert.hasLength(newParentPagePath);
Assert.notNull(user);
// check if pages exist by trying to load them
getPage(projectName, branchName, path, false);
getPage(projectName, branchName, newParentPagePath, false);
ILockedRepository repo = null;
List<String> oldPagePaths;
List<String> deletedPagePaths;
List<String> newPagePaths;
List<PagePathChangedEvent> pagePathChangedEvents;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
String pageName = path.contains("/") ? StringUtils.substringAfterLast(path, "/") : path; //$NON-NLS-1$ //$NON-NLS-2$
final String newPagePath = newParentPagePath + "/" + pageName; //$NON-NLS-1$
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File oldSubPagesDir = Util.toFile(new File(workingDir, DocumentrConstants.PAGES_DIR_NAME), path);
oldPagePaths = listPagePaths(oldSubPagesDir, true);
oldPagePaths = Lists.newArrayList(Lists.transform(oldPagePaths, new Function<String, String>() {
@Override
public String apply(String p) {
return path + "/" + p; //$NON-NLS-1$
}
}));
oldPagePaths.add(path);
File deletedPagesSubDir = Util.toFile(new File(workingDir, DocumentrConstants.PAGES_DIR_NAME), newPagePath);
deletedPagePaths = listPagePaths(deletedPagesSubDir, true);
deletedPagePaths = Lists.newArrayList(Lists.transform(deletedPagePaths, new Function<String, String>() {
@Override
public String apply(String p) {
return newPagePath + "/" + p; //$NON-NLS-1$
}
}));
deletedPagePaths.add(newPagePath);
Git git = Git.wrap(repo.r());
AddCommand addCommand = git.add();
for (String dirName : Sets.newHashSet(DocumentrConstants.PAGES_DIR_NAME, DocumentrConstants.ATTACHMENTS_DIR_NAME)) {
File dir = new File(workingDir, dirName);
File newSubPagesDir = Util.toFile(dir, newPagePath);
if (newSubPagesDir.exists()) {
git.rm().addFilepattern(dirName + "/" + newPagePath).call(); //$NON-NLS-1$
}
File newPageFile = Util.toFile(dir, newPagePath + DocumentrConstants.PAGE_SUFFIX);
if (newPageFile.exists()) {
git.rm().addFilepattern(dirName + "/" + newPagePath + DocumentrConstants.PAGE_SUFFIX).call(); //$NON-NLS-1$
}
File newMetaFile = Util.toFile(dir, newPagePath + DocumentrConstants.META_SUFFIX);
if (newMetaFile.exists()) {
git.rm().addFilepattern(dirName + "/" + newPagePath + DocumentrConstants.META_SUFFIX).call(); //$NON-NLS-1$
}
File newParentPageDir = Util.toFile(dir, newParentPagePath);
File subPagesDir = Util.toFile(dir, path);
if (subPagesDir.exists()) {
FileUtils.copyDirectoryToDirectory(subPagesDir, newParentPageDir);
git.rm().addFilepattern(dirName + "/" + path).call(); //$NON-NLS-1$
addCommand.addFilepattern(dirName + "/" + newParentPagePath + "/" + pageName); //$NON-NLS-1$ //$NON-NLS-2$
}
File pageFile = Util.toFile(dir, path + DocumentrConstants.PAGE_SUFFIX);
if (pageFile.exists()) {
FileUtils.copyFileToDirectory(pageFile, newParentPageDir);
git.rm().addFilepattern(dirName + "/" + path + DocumentrConstants.PAGE_SUFFIX).call(); //$NON-NLS-1$
addCommand.addFilepattern(dirName + "/" + newParentPagePath + "/" + pageName + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$ //$NON-NLS-2$
}
File metaFile = Util.toFile(dir, path + DocumentrConstants.META_SUFFIX);
if (metaFile.exists()) {
FileUtils.copyFileToDirectory(metaFile, newParentPageDir);
git.rm().addFilepattern(dirName + "/" + path + DocumentrConstants.META_SUFFIX).call(); //$NON-NLS-1$
addCommand.addFilepattern(dirName + "/" + newParentPagePath + "/" + pageName + DocumentrConstants.META_SUFFIX); //$NON-NLS-1$ //$NON-NLS-2$
}
}
addCommand.call();
PersonIdent ident = new PersonIdent(user.getLoginName(), user.getEmail());
git.commit()
.setAuthor(ident)
.setCommitter(ident)
.setMessage("move " + path + " to " + newParentPagePath + "/" + pageName) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
.call();
git.push().call();
newPagePaths = Lists.transform(oldPagePaths, new Function<String, String>() {
@Override
public String apply(String p) {
return newPagePath + StringUtils.removeStart(p, path);
}
});
pagePathChangedEvents = Lists.transform(oldPagePaths, new Function<String, PagePathChangedEvent>() {
@Override
public PagePathChangedEvent apply(String oldPath) {
String newPath = newPagePath + StringUtils.removeStart(oldPath, path);
return new PagePathChangedEvent(projectName, branchName, oldPath, newPath);
}
});
PageUtil.updateProjectEditTime(projectName);
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
Set<String> allDeletedPagePaths = Sets.newHashSet(oldPagePaths);
allDeletedPagePaths.addAll(deletedPagePaths);
eventBus.post(new PagesDeletedEvent(projectName, branchName, allDeletedPagePaths));
for (String newPath : newPagePaths) {
eventBus.post(new PageChangedEvent(projectName, branchName, newPath));
}
for (PagePathChangedEvent event : pagePathChangedEvents) {
eventBus.post(event);
}
}
@Override
public Map<String, String> getMarkdown(String projectName, String branchName, String path, Set<String> versions)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
// check if page exists by trying to load it
getPage(projectName, branchName, path, false);
Assert.notEmpty(versions);
Map<String, String> result = Maps.newHashMap();
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
String filePath = DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX; //$NON-NLS-1$
Set<String> realVersions = Sets.newHashSet();
for (String version : versions) {
if (version.equals(VERSION_LATEST)) {
RevCommit latestCommit = CommitUtils.getLastCommit(repo.r(), filePath);
String commitId = latestCommit.getName();
result.put(VERSION_LATEST, commitId);
realVersions.add(commitId);
} else if (version.equals(VERSION_PREVIOUS)) {
RevCommit latestCommit = CommitUtils.getLastCommit(repo.r(), filePath);
if (latestCommit.getParentCount() > 0) {
RevCommit parentCommit = latestCommit.getParent(0);
RevCommit previousCommit = CommitUtils.getLastCommit(repo.r(), parentCommit.getName(), filePath);
if (previousCommit != null) {
String commitId = previousCommit.getName();
result.put(VERSION_PREVIOUS, commitId);
realVersions.add(commitId);
}
}
} else {
realVersions.add(version);
}
}
for (String version : realVersions) {
String markdown = BlobUtils.getContent(repo.r(), version, filePath);
if (markdown != null) {
result.put(version, markdown);
}
}
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
return result;
}
@Override
public List<PageVersion> listPageVersions(String projectName, String branchName, String path) throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
// check if page exists by trying to load it
getPage(projectName, branchName, path, false);
ILockedRepository repo = null;
List<PageVersion> result = Lists.newArrayList();
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
CommitFinder finder = new CommitFinder(repo.r());
TreeFilter pathFilter = PathFilterUtils.or(DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$
finder.setFilter(pathFilter);
CommitListFilter commits = new CommitListFilter();
finder.setMatcher(new AndCommitFilter(new CommitLimitFilter(50), commits));
finder.find();
for (RevCommit commit : commits.getCommits()) {
result.add(PageUtil.toPageVersion(commit));
}
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
return result;
}
@Override
public void deleteAttachment(String projectName, String branchName, String pagePath, String name, User user)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(pagePath);
Assert.hasLength(name);
Assert.notNull(user);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
Git git = Git.wrap(repo.r());
File attachmentsDir = new File(workingDir, DocumentrConstants.ATTACHMENTS_DIR_NAME);
boolean deleted = false;
File file = Util.toFile(attachmentsDir, pagePath + "/" + name + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$
if (file.isFile()) {
FileUtils.forceDelete(file);
git.rm()
.addFilepattern(DocumentrConstants.ATTACHMENTS_DIR_NAME + "/" + //$NON-NLS-1$
pagePath + "/" + name + DocumentrConstants.PAGE_SUFFIX) //$NON-NLS-1$
.call();
deleted = true;
}
file = Util.toFile(attachmentsDir, pagePath + "/" + name + DocumentrConstants.META_SUFFIX); //$NON-NLS-1$
if (file.isFile()) {
FileUtils.forceDelete(file);
git.rm()
.addFilepattern(DocumentrConstants.ATTACHMENTS_DIR_NAME + "/" + //$NON-NLS-1$
pagePath + "/" + name + DocumentrConstants.META_SUFFIX) //$NON-NLS-1$
.call();
deleted = true;
}
if (deleted) {
PersonIdent ident = new PersonIdent(user.getLoginName(), user.getEmail());
git.commit()
.setAuthor(ident)
.setCommitter(ident)
.setMessage("delete " + pagePath + "/" + name) //$NON-NLS-1$ //$NON-NLS-2$
.call();
git.push().call();
PageUtil.updateProjectEditTime(projectName);
}
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
@Override
public void restorePageVersion(String projectName, String branchName, String path, String version, User user)
throws IOException {
Assert.hasLength(projectName);
Assert.hasLength(branchName);
Assert.hasLength(path);
Assert.hasLength(version);
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
String text = BlobUtils.getContent(repo.r(), version, DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX); //$NON-NLS-1$
File workingDir = RepositoryUtil.getWorkingDir(repo.r());
File pagesDir = new File(workingDir, DocumentrConstants.PAGES_DIR_NAME);
File file = Util.toFile(pagesDir, path + DocumentrConstants.PAGE_SUFFIX);
FileUtils.writeStringToFile(file, text, Charsets.UTF_8.name());
Git git = Git.wrap(repo.r());
git.add()
.addFilepattern(DocumentrConstants.PAGES_DIR_NAME + "/" + path + DocumentrConstants.PAGE_SUFFIX) //$NON-NLS-1$
.call();
PersonIdent ident = new PersonIdent(user.getLoginName(), user.getEmail());
git.commit()
.setAuthor(ident)
.setCommitter(ident)
.setMessage(DocumentrConstants.PAGES_DIR_NAME + "/" + path) //$NON-NLS-1$
.call();
git.push().call();
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
eventBus.post(new PageChangedEvent(projectName, branchName, path));
}
@Override
public String getViewRestrictionRole(String projectName, String branchName, String path) throws IOException {
return getPage(projectName, branchName, path, false).getViewRestrictionRole();
}
@Override
public void saveChildrenOrder(String projectName, String branchName, String path, List<String> childPagePathsOrdered,
User user) throws IOException {
List<Page> childPages = Lists.newArrayList();
for (String childPath : childPagePathsOrdered) {
Page page = getPage(projectName, branchName, childPath, false);
childPages.add(page);
}
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
int idx = 0;
for (Page childPage : childPages) {
childPage.setOrderIndex(idx++);
savePageInternal(projectName, branchName, childPage.getPath(), DocumentrConstants.PAGE_SUFFIX, childPage,
null, DocumentrConstants.PAGES_DIR_NAME, user, repo, false);
}
Git.wrap(repo.r()).push().call();
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
@Override
public void resetChildrenOrder(String projectName, String branchName, String path, User user) throws IOException {
List<String> childPaths = listChildPagePaths(projectName, branchName, path);
Set<Page> childPages = Sets.newHashSet();
for (String childPath : childPaths) {
Page page = getPage(projectName, branchName, childPath, false);
childPages.add(page);
}
ILockedRepository repo = null;
try {
repo = globalRepositoryManager.getProjectBranchRepository(projectName, branchName);
for (Page childPage : childPages) {
childPage.setOrderIndex(DocumentrConstants.PAGE_ORDER_INDEX_UNORDERED);
savePageInternal(projectName, branchName, childPage.getPath(), DocumentrConstants.PAGE_SUFFIX, childPage,
null, DocumentrConstants.PAGES_DIR_NAME, user, repo, false);
}
Git.wrap(repo.r()).push().call();
} catch (GitAPIException e) {
throw new IOException(e);
} finally {
Util.closeQuietly(repo);
}
}
}