From 13a3f5bc3e2d25fc76850f86070dc34efe60d77a Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 07 Sep 2012 22:06:15 -0400
Subject: [PATCH] Draft project pages, project metadata, and RSS feeds

---
 src/com/gitblit/GitBlit.java |  135 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 135 insertions(+), 0 deletions(-)

diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index e6effc2..c758654 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -37,6 +37,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
@@ -79,6 +80,7 @@
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.FederationSet;
 import com.gitblit.models.Metric;
+import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.SearchResult;
 import com.gitblit.models.ServerSettings;
@@ -132,6 +134,8 @@
 	
 	private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();
 	
+	private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
+	
 	private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
 
 	private RepositoryResolver<Void> repositoryResolver;
@@ -153,6 +157,8 @@
 	private LuceneExecutor luceneExecutor;
 	
 	private TimeZone timezone;
+	
+	private FileBasedConfig projectConfigs;
 
 	public GitBlit() {
 		if (gitblit == null) {
@@ -1018,6 +1024,130 @@
 		
 		// return a copy of the cached model
 		return DeepCopier.copy(model);
+	}
+	
+	
+	/**
+	 * 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<String, ProjectModel> getProjectConfigs() {
+		if (projectConfigs.isOutdated()) {
+			
+			try {
+				projectConfigs.load();
+			} catch (Exception e) {
+			}
+
+			// project configs
+			String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
+			ProjectModel rootProject = new ProjectModel(rootName, true);
+
+			Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
+			// 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");
+				// TODO add more interesting metadata
+				// project manager?
+				// commit message regex?
+				// RW+
+				// RW
+				// R
+				configs.put(name.toLowerCase(), project);				
+			}
+			projectCache.clear();
+			projectCache.putAll(configs);
+		}
+		return projectCache;
+	}
+	
+	/**
+	 * Returns a list of project models for the user.
+	 * 
+	 * @param user
+	 * @return list of projects that are accessible to the user
+	 */
+	public List<ProjectModel> getProjectModels(UserModel user) {
+		Map<String, ProjectModel> configs = getProjectConfigs();
+
+		// per-user project lists, this accounts for security and visibility
+		Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>();
+		// root project
+		map.put("", configs.get(""));
+		
+		for (RepositoryModel model : getRepositoryModels(user)) {
+			String rootPath = StringUtils.getRootPath(model.name).toLowerCase();			
+			if (!map.containsKey(rootPath)) {
+				ProjectModel project;
+				if (configs.containsKey(rootPath)) {
+					// clone the project model because it's repository list will
+					// be tailored for the requesting user
+					project = DeepCopier.copy(configs.get(rootPath));
+				} else {
+					project = new ProjectModel(rootPath);
+				}
+				map.put(rootPath, project);
+			}
+			map.get(rootPath).addRepository(model);
+		}
+		
+		// sort projects, root project first
+		List<ProjectModel> projects = new ArrayList<ProjectModel>(map.values());
+		Collections.sort(projects);
+		projects.remove(map.get(""));
+		projects.add(0, map.get(""));
+		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
+	 */
+	public ProjectModel getProjectModel(String name, UserModel user) {
+		for (ProjectModel project : getProjectModels(user)) {
+			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
+	 */
+	public ProjectModel getProjectModel(String name) {
+		Map<String, ProjectModel> configs = getProjectConfigs();
+		ProjectModel project = configs.get(name.toLowerCase());
+		if (project == null) {
+			return null;
+		}
+		// clone the object
+		project = DeepCopier.copy(project);
+		String folder = name.toLowerCase() + "/";
+		for (String repository : getRepositoryList()) {
+			if (repository.toLowerCase().startsWith(folder)) {
+				project.addRepository(repository);
+			}
+		}
+		return project;
 	}
 	
 	/**
@@ -2180,6 +2310,11 @@
 			loginService = new GitblitUserService();
 		}
 		setUserService(loginService);
+		
+		// load and cache the project metadata
+		projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "projects.conf"), FS.detect());
+		getProjectConfigs();
+		
 		mailExecutor = new MailExecutor(settings);
 		if (mailExecutor.isReady()) {
 			logger.info("Mail executor is scheduled to process the message queue every 2 minutes.");

--
Gitblit v1.9.1