/* * Copyright 2013 gitblit.com. * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.gitblit.manager; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.ModelUtils; import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Project manager handles project-related functions. * * @author James Moger * */ @Singleton public class ProjectManager implements IProjectManager { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Map projectCache = new ConcurrentHashMap(); private final ObjectCache projectMarkdownCache = new ObjectCache(); private final ObjectCache projectRepositoriesMarkdownCache = new ObjectCache(); private final IStoredSettings settings; private final IRuntimeManager runtimeManager; private final IUserManager userManager; private final IRepositoryManager repositoryManager; private FileBasedConfig projectConfigs; @Inject public ProjectManager( IRuntimeManager runtimeManager, IUserManager userManager, IRepositoryManager repositoryManager) { this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; this.userManager = userManager; this.repositoryManager = repositoryManager; } @Override public ProjectManager start() { // load and cache the project metadata projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect()); getProjectConfigs(); return this; } @Override public ProjectManager stop() { return this; } private void reloadProjectMarkdown(ProjectModel project) { // project markdown File pmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd"); if (pmkd.exists()) { Date lm = new Date(pmkd.lastModified()); if (!projectMarkdownCache.hasCurrent(project.name, lm)) { String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n"); projectMarkdownCache.updateObject(project.name, lm, mkd); } project.projectMarkdown = projectMarkdownCache.getObject(project.name); } // project repositories markdown File rmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd"); if (rmkd.exists()) { Date lm = new Date(rmkd.lastModified()); if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) { String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n"); projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd); } project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name); } } /** * Returns the map of project config. This map is cached and reloaded if * the underlying projects.conf file changes. * * @return project config map */ private Map getProjectConfigs() { if (projectCache.isEmpty() || projectConfigs.isOutdated()) { try { projectConfigs.load(); } catch (Exception e) { } // project configs String rootName = settings.getString(Keys.web.repositoryRootGroupName, "main"); ProjectModel rootProject = new ProjectModel(rootName, true); Map configs = new HashMap(); // cache the root project under its alias and an empty path configs.put("", rootProject); configs.put(rootProject.name.toLowerCase(), rootProject); for (String name : projectConfigs.getSubsections("project")) { ProjectModel project; if (name.equalsIgnoreCase(rootName)) { project = rootProject; } else { project = new ProjectModel(name); } project.title = projectConfigs.getString("project", name, "title"); project.description = projectConfigs.getString("project", name, "description"); reloadProjectMarkdown(project); configs.put(name.toLowerCase(), project); } projectCache.clear(); projectCache.putAll(configs); } return projectCache; } /** * Returns a list of project models for the user. * * @param user * @param includeUsers * @return list of projects that are accessible to the user */ @Override public List getProjectModels(UserModel user, boolean includeUsers) { Map configs = getProjectConfigs(); // per-user project lists, this accounts for security and visibility Map map = new TreeMap(); // root project map.put("", configs.get("")); for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) { String projectPath = StringUtils.getRootPath(model.name); String projectKey = projectPath.toLowerCase(); if (!map.containsKey(projectKey)) { ProjectModel project; if (configs.containsKey(projectKey)) { // clone the project model because it's repository list will // be tailored for the requesting user project = DeepCopier.copy(configs.get(projectKey)); } else { project = new ProjectModel(projectPath); } map.put(projectKey, project); } map.get(projectKey).addRepository(model); } // sort projects, root project first List projects; if (includeUsers) { // all projects projects = new ArrayList(map.values()); Collections.sort(projects); projects.remove(map.get("")); projects.add(0, map.get("")); } else { // all non-user projects projects = new ArrayList(); ProjectModel root = map.remove(""); for (ProjectModel model : map.values()) { if (!model.isUserProject()) { projects.add(model); } } Collections.sort(projects); projects.add(0, root); } return projects; } /** * Returns the project model for the specified user. * * @param name * @param user * @return a project model, or null if it does not exist */ @Override public ProjectModel getProjectModel(String name, UserModel user) { for (ProjectModel project : getProjectModels(user, true)) { if (project.name.equalsIgnoreCase(name)) { return project; } } return null; } /** * Returns a project model for the Gitblit/system user. * * @param name a project name * @return a project model or null if the project does not exist */ @Override public ProjectModel getProjectModel(String name) { Map configs = getProjectConfigs(); ProjectModel project = configs.get(name.toLowerCase()); if (project == null) { project = new ProjectModel(name); if (ModelUtils.isPersonalRepository(name)) { UserModel user = userManager.getUserModel(ModelUtils.getUserNameFromRepoPath(name)); if (user != null) { project.title = user.getDisplayName(); project.description = "personal repositories"; } } } else { // clone the object project = DeepCopier.copy(project); } if (StringUtils.isEmpty(name)) { // get root repositories for (String repository : repositoryManager.getRepositoryList()) { if (repository.indexOf('/') == -1) { project.addRepository(repository); } } } else { // get repositories in subfolder String folder = name.toLowerCase() + "/"; for (String repository : repositoryManager.getRepositoryList()) { if (repository.toLowerCase().startsWith(folder)) { project.addRepository(repository); } } } if (project.repositories.size() == 0) { // no repositories == no project return null; } reloadProjectMarkdown(project); return project; } /** * Returns the list of project models that are referenced by the supplied * repository model list. This is an alternative method exists to ensure * Gitblit does not call getRepositoryModels(UserModel) twice in a request. * * @param repositoryModels * @param includeUsers * @return a list of project models */ @Override public List getProjectModels(List repositoryModels, boolean includeUsers) { Map projects = new LinkedHashMap(); for (RepositoryModel repository : repositoryModels) { if (!includeUsers && repository.isPersonalRepository()) { // exclude personal repositories continue; } if (!projects.containsKey(repository.projectPath)) { ProjectModel project = getProjectModel(repository.projectPath); if (project == null) { logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!", repository.projectPath)); continue; } projects.put(repository.projectPath, project); // clear the repo list in the project because that is the system // list, not the user-accessible list and start building the // user-accessible list project.repositories.clear(); project.repositories.add(repository.name); project.lastChange = repository.lastChange; } else { // update the user-accessible list // this is used for repository count ProjectModel project = projects.get(repository.projectPath); project.repositories.add(repository.name); if (project.lastChange.before(repository.lastChange)) { project.lastChange = repository.lastChange; } } } return new ArrayList(projects.values()); } }