Add paginator and separate search code in controller to service.

This commit is contained in:
takezoe
2013-07-19 20:24:31 +09:00
parent f4a5e18c69
commit 54280d5572
4 changed files with 140 additions and 120 deletions

View File

@@ -1,21 +1,16 @@
package app package app
import util._ import util._
import util.Directory._
import service._ import service._
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.jgit.revwalk.RevWalk
import scala.collection.mutable.ListBuffer
import org.eclipse.jgit.lib.FileMode
import model.Issue
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase
with RepositoryService with AccountService with SystemSettingsService with ActivityService with IssuesService with RepositoryService with AccountService with SystemSettingsService with ActivityService
with RepositorySearchService with IssuesService
with ReferrerAuthenticator with ReferrerAuthenticator
trait IndexControllerBase extends ControllerBase { self: RepositoryService trait IndexControllerBase extends ControllerBase { self: RepositoryService
with SystemSettingsService with ActivityService with IssuesService with SystemSettingsService with ActivityService with RepositorySearchService
with ReferrerAuthenticator => with ReferrerAuthenticator =>
val searchForm = mapping( val searchForm = mapping(
@@ -41,7 +36,6 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
} }
get("/:owner/:repository/search")(referrersOnly { repository => get("/:owner/:repository/search")(referrersOnly { repository =>
import RepositorySearch._
val query = params("q").trim val query = params("q").trim
val target = params.getOrElse("type", "code") val target = params.getOrElse("type", "code")
val page = try { val page = try {
@@ -51,114 +45,17 @@ trait IndexControllerBase extends ControllerBase { self: RepositoryService
case e: NumberFormatException => 1 case e: NumberFormatException => 1
} }
val SearchResult(files, issues) = searchRepository(repository.owner, repository.name, query)
target.toLowerCase match { target.toLowerCase match {
case "issue" => case "issue" => search.html.issues(
search.html.issues(issues.map { case (issue, commentCount, content) => searchIssues(repository.owner, repository.name, query),
IssueSearchResult( countFiles(repository.owner, repository.name, query),
issue.issueId, query, page, repository)
issue.title,
issue.openedUserName,
issue.registeredDate,
commentCount,
getHighlightText(content, query)._1)
}, files.size, query, page, repository)
case _ =>
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
search.html.code(files.toList.map { case (path, text) => case _ => search.html.code(
val (highlightText, lineNumber) = getHighlightText(text, query) searchFiles(repository.owner, repository.name, query),
FileSearchResult( countIssues(repository.owner, repository.name, query),
path, query, page, repository)
commits(path).getCommitterIdent.getWhen,
highlightText,
lineNumber)
}, issues.size, query, page, repository)
}
} }
}) })
case class SearchResult(
files: List[(String, String)],
issues: List[(Issue, Int, String)]
)
def searchRepository(owner: String, repository: String, query: String): SearchResult = {
val issues = if(query.isEmpty) Nil else searchIssuesByKeyword(owner, repository, query)
val files = if(query.isEmpty) Nil else searchRepositoryFiles(owner, repository, query)
SearchResult(files, issues)
}
private def searchRepositoryFiles(owner: String, repository: String, query: String): List[(String, String)] = {
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve("HEAD")
val revCommit = revWalk.parseCommit(objectId)
val treeWalk = new TreeWalk(git.getRepository)
treeWalk.setRecursive(true)
treeWalk.addTree(revCommit.getTree)
val keywords = StringUtil.splitWords(query.toLowerCase)
val list = new ListBuffer[(String, String)]
while (treeWalk.next()) {
if(treeWalk.getFileMode(0) != FileMode.TREE){
JGitUtil.getContent(git, treeWalk.getObjectId(0), false).foreach { bytes =>
if(FileUtil.isText(bytes)){
val text = new String(bytes, "UTF-8")
val lowerText = text.toLowerCase
val indices = keywords.map(lowerText.indexOf _)
if(!indices.exists(_ < 0)){
list.append((treeWalk.getPathString, text))
}
}
}
}
}
treeWalk.release
revWalk.release
list.toList
}
}
private def getHighlightText(content: String, query: String): (String, Int) = {
val keywords = StringUtil.splitWords(query.toLowerCase)
val lowerText = content.toLowerCase
val indices = keywords.map(lowerText.indexOf _)
if(!indices.exists(_ < 0)){
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
.replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")",
"<span style=\"background-color: #ffff88;;\">$1</span>")
(highlightText, lineNumber + 1)
} else {
(content.split("\n").take(5).mkString("\n"), 1)
}
}
} }
case class IssueSearchResult(
issueId: Int,
title: String,
openedUserName: String,
registeredDate: java.util.Date,
commentCount: Int,
highlightText: String)
case class FileSearchResult(
path: String,
lastModified: java.util.Date,
highlightText: String,
highlightLineNumber: Int)
object RepositorySearch extends IssuesService {
val CodeLimit = 10
val IssueLimit = 10
}

View File

@@ -0,0 +1,121 @@
package service
import model.Issue
import util.{FileUtil, StringUtil, JGitUtil}
import util.Directory._
import model.Issue
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.treewalk.TreeWalk
import scala.collection.mutable.ListBuffer
import org.eclipse.jgit.lib.FileMode
import org.eclipse.jgit.api.Git
trait RepositorySearchService { self: IssuesService =>
import RepositorySearchService._
def countIssues(owner: String, repository: String, query: String): Int =
searchIssuesByKeyword(owner, repository, query).length
def searchIssues(owner: String, repository: String, query: String): List[IssueSearchResult] =
searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) =>
IssueSearchResult(
issue.issueId,
issue.title,
issue.openedUserName,
issue.registeredDate,
commentCount,
getHighlightText(content, query)._1)
}
def countFiles(owner: String, repository: String, query: String): Int =
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
searchRepositoryFiles(git, query).length
}
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
val files = searchRepositoryFiles(git, query)
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
files.map { case (path, text) =>
val (highlightText, lineNumber) = getHighlightText(text, query)
FileSearchResult(
path,
commits(path).getCommitterIdent.getWhen,
highlightText,
lineNumber)
}
}
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve("HEAD")
val revCommit = revWalk.parseCommit(objectId)
val treeWalk = new TreeWalk(git.getRepository)
treeWalk.setRecursive(true)
treeWalk.addTree(revCommit.getTree)
val keywords = StringUtil.splitWords(query.toLowerCase)
val list = new ListBuffer[(String, String)]
while (treeWalk.next()) {
if(treeWalk.getFileMode(0) != FileMode.TREE){
JGitUtil.getContent(git, treeWalk.getObjectId(0), false).foreach { bytes =>
if(FileUtil.isText(bytes)){
val text = new String(bytes, "UTF-8")
val lowerText = text.toLowerCase
val indices = keywords.map(lowerText.indexOf _)
if(!indices.exists(_ < 0)){
list.append((treeWalk.getPathString, text))
}
}
}
}
}
treeWalk.release
revWalk.release
list.toList
}
}
object RepositorySearchService {
val CodeLimit = 10
val IssueLimit = 10
def getHighlightText(content: String, query: String): (String, Int) = {
val keywords = StringUtil.splitWords(query.toLowerCase)
val lowerText = content.toLowerCase
val indices = keywords.map(lowerText.indexOf _)
if(!indices.exists(_ < 0)){
val lineNumber = content.substring(0, indices.min).split("\n").size - 1
val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n"))
.replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")",
"<span style=\"background-color: #ffff88;;\">$1</span>")
(highlightText, lineNumber + 1)
} else {
(content.split("\n").take(5).mkString("\n"), 1)
}
}
case class SearchResult(
files : List[(String, String)],
issues: List[(Issue, Int, String)])
case class IssueSearchResult(
issueId: Int,
title: String,
openedUserName: String,
registeredDate: java.util.Date,
commentCount: Int,
highlightText: String)
case class FileSearchResult(
path: String,
lastModified: java.util.Date,
highlightText: String,
highlightLineNumber: Int)
}

View File

@@ -1,10 +1,11 @@
@(files: List[app.FileSearchResult], @(files: List[service.RepositorySearchService.FileSearchResult],
issueCount: Int, issueCount: Int,
query: String, query: String,
page: Int, page: Int,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import service.RepositorySearchService._
@html.main("Search Results", Some(repository)){ @html.main("Search Results", Some(repository)){
@menu("code", files.size, issueCount, query, repository){ @menu("code", files.size, issueCount, query, repository){
@if(files.isEmpty){ @if(files.isEmpty){
@@ -12,14 +13,14 @@
} else { } else {
<h4>We've found @files.size code @plural(files.size, "result")</h4> <h4>We've found @files.size code @plural(files.size, "result")</h4>
} }
@files.drop((page - 1) * app.RepositorySearch.CodeLimit).take(app.RepositorySearch.CodeLimit).map { file => @files.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
<div> <div>
<h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5> <h5><a href="@url(repository)/blob/@repository.repository.defaultBranch/@file.path">@file.path</a></h5>
<div class="small muted">Latest commit at @datetime(file.lastModified)</div> <div class="small muted">Latest commit at @datetime(file.lastModified)</div>
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre> <pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
</div> </div>
} }
@helper.html.paginator(page, files.size, app.RepositorySearch.CodeLimit, 10, @helper.html.paginator(page, files.size, CodeLimit, 10,
s"${url(repository)}/search?q=${urlEncode(query)}&type=code") s"${url(repository)}/search?q=${urlEncode(query)}&type=code")
} }
} }

View File

@@ -1,10 +1,11 @@
@(issues: List[app.IssueSearchResult], @(issues: List[service.RepositorySearchService.IssueSearchResult],
fileCount: Int, fileCount: Int,
query: String, query: String,
page: Int, page: Int,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import service.RepositorySearchService._
@html.main("Search Results", Some(repository)){ @html.main("Search Results", Some(repository)){
@menu("issue", fileCount, issues.size, query, repository){ @menu("issue", fileCount, issues.size, query, repository){
@if(issues.isEmpty){ @if(issues.isEmpty){
@@ -12,7 +13,7 @@
} else { } else {
<h4>We've found @issues.size code @plural(issues.size, "result")</h4> <h4>We've found @issues.size code @plural(issues.size, "result")</h4>
} }
@issues.drop((page - 1) * app.RepositorySearch.IssueLimit).take(app.RepositorySearch.IssueLimit).map { issue => @issues.drop((page - 1) * IssueLimit).take(IssueLimit).map { issue =>
<div class="block"> <div class="block">
<div class="pull-right muted">#@issue.issueId</div> <div class="pull-right muted">#@issue.issueId</div>
<h4 style="margin-top: 0px;"><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4> <h4 style="margin-top: 0px;"><a href="@url(repository)/issues/@issue.issueId">@issue.title</a></h4>
@@ -26,7 +27,7 @@
</div> </div>
</div> </div>
} }
@helper.html.paginator(page, issues.size, app.RepositorySearch.IssueLimit, 10, @helper.html.paginator(page, issues.size, IssueLimit, 10,
s"${url(repository)}/search?q=${urlEncode(query)}&type=issue") s"${url(repository)}/search?q=${urlEncode(query)}&type=issue")
} }
} }