diff --git a/build.sbt b/build.sbt index df14eb8..e66d09d 100644 --- a/build.sbt +++ b/build.sbt @@ -54,11 +54,9 @@ "ch.qos.logback" % "logback-classic" % "1.2.3", "com.zaxxer" % "HikariCP" % "3.4.5", "com.typesafe" % "config" % "1.4.0", - "com.typesafe.akka" %% "akka-actor" % "2.5.27", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0", "com.github.bkromhout" % "java-diff-utils" % "2.1.1", "org.cache2k" % "cache2k-all" % "1.2.4.Final", - "com.enragedginger" %% "akka-quartz-scheduler" % "1.8.1-akka-2.5.x" exclude ("com.mchange", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"), "net.coobird" % "thumbnailator" % "0.4.11", "com.github.zafarkhaja" % "java-semver" % "0.9.0", "com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4", diff --git a/src/main/resources/update/gitbucket-core_4.34.xml b/src/main/resources/update/gitbucket-core_4.34.xml new file mode 100644 index 0000000..4094e6a --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.34.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<changeSet> + <dropTable tableName="ACTIVITY" /> +</changeSet> diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 5255e17..dd00503 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -1,7 +1,22 @@ package gitbucket.core -import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration} -import io.github.gitbucket.solidbase.model.{Version, Module} +import java.io.FileOutputStream +import java.nio.charset.StandardCharsets +import java.sql.Connection +import java.util +import java.util.UUID + +import gitbucket.core.model.Activity +import gitbucket.core.util.Directory.ActivityLog +import gitbucket.core.util.JDBCUtil +import io.github.gitbucket.solidbase.Solidbase +import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration} +import io.github.gitbucket.solidbase.model.{Module, Version} +import org.json4s.NoTypeHints +import org.json4s.jackson.Serialization +import org.json4s.jackson.Serialization.write + +import scala.util.Using object GitBucketCoreModule extends Module( @@ -65,5 +80,38 @@ new Version("4.31.1"), new Version("4.31.2"), new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")), - new Version("4.33.0") + new Version("4.33.0"), + new Version( + "4.34.0", + new Migration() { + override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = { + implicit val formats = Serialization.formats(NoTypeHints) + import JDBCUtil._ + + val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection] + val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") { + rs => + Activity( + activityId = UUID.randomUUID().toString, + userName = rs.getString("USER_NAME"), + repositoryName = rs.getString("REPOSITORY_NAME"), + activityUserName = rs.getString("ACTIVITY_USER_NAME"), + activityType = rs.getString("ACTIVITY_TYPE"), + message = rs.getString("MESSAGE"), + additionalInfo = { + val additionalInfo = rs.getString("ADDITIONAL_INFO") + if (rs.wasNull()) None else Some(additionalInfo) + }, + activityDate = rs.getTimestamp("ACTIVITY_DATE") + ) + } + Using.resource(new FileOutputStream(ActivityLog, true)) { out => + list.foreach { activity => + out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8)) + } + } + } + }, + new LiquibaseMigration("update/gitbucket-core_4.34.xml") + ) ) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index f187e29..f4978d4 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -35,6 +35,7 @@ with WebHookService with PrioritiesService with RepositoryCreationService + with RequestCache trait AccountControllerBase extends AccountManagementControllerBase { self: AccountService diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index f84111a..07898e1 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -52,6 +52,7 @@ with ReferrerAuthenticator with ReadableUsersAuthenticator with WritableUsersAuthenticator + with RequestCache trait ApiControllerBase extends ControllerBase { diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index 5355bc1..bae642d 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -22,6 +22,7 @@ with WebHookPullRequestReviewCommentService with MilestonesService with UsersAuthenticator + with RequestCache trait DashboardControllerBase extends ControllerBase { self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator => diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index 15feaa8..cddd77a 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -29,6 +29,7 @@ with AccessTokenService with AccountFederationService with OpenIDConnectService + with RequestCache trait IndexControllerBase extends ControllerBase { self: RepositoryService @@ -78,7 +79,7 @@ } .getOrElse { gitbucket.core.html.index( - getRecentActivities(), + getRecentPublicActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), showBannerToCreatePersonalAccessToken = false ) @@ -161,7 +162,7 @@ get("/activities.atom") { contentType = "application/atom+xml; type=feed" - xml.feed(getRecentActivities()) + xml.feed(getRecentPublicActivities()) } post("/sidebar-collapse") { diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index f19e7a9..20470e4 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -30,6 +30,7 @@ with WebHookPullRequestReviewCommentService with CommitsService with PrioritiesService + with RequestCache trait IssuesControllerBase extends ControllerBase { self: IssuesService diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 15d18ef..43b7f12 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -36,6 +36,7 @@ with MergeService with ProtectedBranchService with PrioritiesService + with RequestCache trait PullRequestsControllerBase extends ControllerBase { self: RepositoryService diff --git a/src/main/scala/gitbucket/core/controller/ReleasesController.scala b/src/main/scala/gitbucket/core/controller/ReleasesController.scala index b0ab907..d2cee57 100644 --- a/src/main/scala/gitbucket/core/controller/ReleasesController.scala +++ b/src/main/scala/gitbucket/core/controller/ReleasesController.scala @@ -2,7 +2,14 @@ import java.io.File -import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService} +import gitbucket.core.service.{ + AccountService, + ActivityService, + PaginationHelper, + ReleaseService, + RepositoryService, + RequestCache +} import gitbucket.core.util._ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ @@ -22,6 +29,7 @@ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator + with RequestCache trait ReleaseControllerBase extends ControllerBase { self: RepositoryService diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 0e34d94..e35741e 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -30,8 +30,10 @@ with ProtectedBranchService with CommitStatusService with DeployKeyService + with ActivityService with OwnerAuthenticator with UsersAuthenticator + with RequestCache trait RepositorySettingsControllerBase extends ControllerBase { self: RepositoryService @@ -40,6 +42,7 @@ with ProtectedBranchService with CommitStatusService with DeployKeyService + with ActivityService with OwnerAuthenticator with UsersAuthenticator => @@ -97,9 +100,7 @@ "events" -> webhookEvents, "ctype" -> label("ctype", text()), "token" -> optional(trim(label("token", text(maxlength(100))))) - )( - (url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token) - ) + )((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)) // for rename repository case class RenameRepositoryForm(repositoryName: String) @@ -251,9 +252,10 @@ * Send the test request to registered web hook URLs. */ ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => - def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => - Array(h.getName, h.getValue) - } + def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = + h.map { h => + Array(h.getName, h.getValue) + } Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => @@ -371,7 +373,15 @@ post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) => if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) { if (repository.name != form.repositoryName) { + // Update database and move git repository renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) + // Record activity log + recordRenameRepositoryActivity( + repository.owner, + form.repositoryName, + repository.name, + context.loginAccount.get.userName + ) } redirect(s"/${repository.owner}/${form.repositoryName}") } else Forbidden() @@ -384,7 +394,15 @@ if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) { // Change repository owner if (repository.owner != form.newOwner) { + // Update database and move git repository renameRepository(repository.owner, repository.name, form.newOwner, repository.name) + // Record activity log + recordRenameRepositoryActivity( + form.newOwner, + repository.name, + repository.owner, + context.loginAccount.get.userName + ) } redirect(s"/${form.newOwner}/${repository.name}") } else Forbidden() @@ -435,32 +453,34 @@ /** * Provides duplication check for web hook url. */ - private def webHook(needExists: Boolean): Constraint = new Constraint() { - override def validate(name: String, value: String, messages: Messages): Option[String] = - if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) { - Some(if (needExists) { - "URL had not been registered yet." + private def webHook(needExists: Boolean): Constraint = + new Constraint() { + override def validate(name: String, value: String, messages: Messages): Option[String] = + if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) { + Some(if (needExists) { + "URL had not been registered yet." + } else { + "URL had been registered already." + }) } else { - "URL had been registered already." - }) - } else { - None - } - } - - private def webhookEvents = new ValueType[Set[WebHook.Event]] { - def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { - WebHook.Event.values.flatMap { t => - params.get(name + "." + t.name).map(_ => t) - }.toSet + None + } } - def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = - if (convert(name, params, messages).isEmpty) { - Seq(name -> messages("error.required").format(name)) - } else { - Nil + + private def webhookEvents = + new ValueType[Set[WebHook.Event]] { + def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { + WebHook.Event.values.flatMap { t => + params.get(name + "." + t.name).map(_ => t) + }.toSet } - } + def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = + if (convert(name, params, messages).isEmpty) { + Seq(name -> messages("error.required").format(name)) + } else { + Nil + } + } // /** // * Provides Constraint to validate the collaborator name. @@ -480,70 +500,77 @@ /** * Duplicate check for the rename repository name. */ - private def renameRepositoryName: Constraint = new Constraint() { - override def validate( - name: String, - value: String, - params: Map[String, Seq[String]], - messages: Messages - ): Option[String] = { - for { - repoName <- params.optionValue("repository") if repoName != value - userName <- params.optionValue("owner") - _ <- getRepositoryNamesOfUser(userName).find(_ == value) - } yield { - "Repository already exists." + private def renameRepositoryName: Constraint = + new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { + for { + repoName <- params.optionValue("repository") if repoName != value + userName <- params.optionValue("owner") + _ <- getRepositoryNamesOfUser(userName).find(_ == value) + } yield { + "Repository already exists." + } } } - } /** - * */ - private def featureOption: Constraint = new Constraint() { - override def validate( - name: String, - value: String, - params: Map[String, Seq[String]], - messages: Messages - ): Option[String] = - if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") - } + private def featureOption: Constraint = + new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = + if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") + } /** * Provides Constraint to validate the repository transfer user. */ - private def transferUser: Constraint = new Constraint() { - override def validate(name: String, value: String, messages: Messages): Option[String] = - getAccountByUserName(value) match { - case None => Some("User does not exist.") - case Some(x) => - if (x.userName == params("owner")) { - Some("This is current repository owner.") - } else { - params.get("repository").flatMap { repositoryName => - getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ => - "User already has same repository." + private def transferUser: Constraint = + new Constraint() { + override def validate(name: String, value: String, messages: Messages): Option[String] = + getAccountByUserName(value) match { + case None => Some("User does not exist.") + case Some(x) => + if (x.userName == params("owner")) { + Some("This is current repository owner.") + } else { + params.get("repository").flatMap { repositoryName => + getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ => + "User already has same repository." + } } } - } - } - } + } + } - private def mergeOptions = new ValueType[Seq[String]] { - override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = { - params.getOrElse("mergeOptions", Nil) - } - override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = { - val mergeOptions = params.getOrElse("mergeOptions", Nil) - if (mergeOptions.isEmpty) { - Seq("mergeOptions" -> "At least one option must be enabled.") - } else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) { - Seq("mergeOptions" -> "mergeOptions are invalid.") - } else { - Nil + private def mergeOptions = + new ValueType[Seq[String]] { + override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = { + params.getOrElse("mergeOptions", Nil) + } + override def validate( + name: String, + params: Map[String, Seq[String]], + messages: Messages + ): Seq[(String, String)] = { + val mergeOptions = params.getOrElse("mergeOptions", Nil) + if (mergeOptions.isEmpty) { + Seq("mergeOptions" -> "At least one option must be enabled.") + } else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) { + Seq("mergeOptions" -> "mergeOptions are invalid.") + } else { + Nil + } } } - } } diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index a6c8793..da2b5ba 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -60,6 +60,7 @@ with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService + with RequestCache /** * The repository viewer. diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index d7de385..428e80f 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -24,6 +24,7 @@ with WebHookService with ReadableUsersAuthenticator with ReferrerAuthenticator + with RequestCache trait WikiControllerBase extends ControllerBase { self: WikiService diff --git a/src/main/scala/gitbucket/core/model/Activity.scala b/src/main/scala/gitbucket/core/model/Activity.scala index 35c990e..a7ca6fb 100644 --- a/src/main/scala/gitbucket/core/model/Activity.scala +++ b/src/main/scala/gitbucket/core/model/Activity.scala @@ -1,5 +1,9 @@ package gitbucket.core.model +/** + * ActivityComponent has been deprecated, but keep it for binary compatibility. + */ +@deprecated("ActivityComponent has been deprecated, but keep it for binary compatibility.", "4.34.0") trait ActivityComponent extends TemplateComponent { self: Profile => import profile.api._ import self._ @@ -7,14 +11,7 @@ lazy val Activities = TableQuery[Activities] class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate { - val activityId = column[Int]("ACTIVITY_ID", O AutoInc) - val activityUserName = column[String]("ACTIVITY_USER_NAME") - val activityType = column[String]("ACTIVITY_TYPE") - val message = column[String]("MESSAGE") - val additionalInfo = column[String]("ADDITIONAL_INFO") - val activityDate = column[java.util.Date]("ACTIVITY_DATE") - def * = - (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply) + def * = ??? } } @@ -26,5 +23,5 @@ message: String, additionalInfo: Option[String], activityDate: java.util.Date, - activityId: Int = 0 + activityId: String ) diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala index ce1f96b..4351808 100644 --- a/src/main/scala/gitbucket/core/model/Profile.scala +++ b/src/main/scala/gitbucket/core/model/Profile.scala @@ -45,7 +45,7 @@ with Profile with AccessTokenComponent with AccountComponent - with ActivityComponent + with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility with CollaboratorComponent with CommitCommentComponent with CommitStatusComponent diff --git a/src/main/scala/gitbucket/core/service/ActivityService.scala b/src/main/scala/gitbucket/core/service/ActivityService.scala index 5064630..9b6dd8a 100644 --- a/src/main/scala/gitbucket/core/service/ActivityService.scala +++ b/src/main/scala/gitbucket/core/service/ActivityService.scala @@ -3,65 +3,169 @@ import gitbucket.core.model.Activity import gitbucket.core.util.JGitUtil import gitbucket.core.model.Profile._ -import gitbucket.core.model.Profile.profile.blockingApi._ +import gitbucket.core.util.Directory._ +import org.json4s._ +import org.json4s.jackson.Serialization +import org.json4s.jackson.Serialization.{read, write} + +import scala.util.Using +import java.io.FileOutputStream +import java.nio.charset.StandardCharsets +import java.util.UUID + +import gitbucket.core.controller.Context +import org.apache.commons.io.input.ReversedLinesFileReader + +import scala.collection.mutable.ListBuffer trait ActivityService { + self: RequestCache => - def deleteOldActivities(limit: Int)(implicit s: Session): Int = { - Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id => - Activities.filter(_.activityId <= id.bind).delete - } getOrElse 0 + private implicit val formats = Serialization.formats(NoTypeHints) + + private def writeLog(activity: Activity): Unit = { + Using.resource(new FileOutputStream(ActivityLog, true)) { out => + out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8)) + } } - def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = - Activities - .join(Repositories) - .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { - case (t1, t2) => - if (isPublic) { - (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) - } else { - (t1.activityUserName === activityUserName.bind) + def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = { + if (!ActivityLog.exists()) { + List.empty + } else { + val list = new ListBuffer[Activity] + Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => + var json: String = null + while (list.length < 50 && { json = reader.readLine(); json } != null) { + val activity = read[Activity](json) + if (activity.activityUserName == activityUserName) { + if (isPublic == false) { + list += activity + } else { + if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) + .map(_.isPrivate) + .getOrElse(true)) { + list += activity + } + } } + } } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list + list.toList + } + } - def getRecentActivities()(implicit s: Session): List[Activity] = - Activities - .join(Repositories) - .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => t2.isPrivate === false.bind } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list + def getRecentPublicActivities()(implicit context: Context): List[Activity] = { + if (!ActivityLog.exists()) { + List.empty + } else { + val list = new ListBuffer[Activity] + Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => + var json: String = null + while (list.length < 50 && { json = reader.readLine(); json } != null) { + val activity = read[Activity](json) + if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) + .map(_.isPrivate) + .getOrElse(true)) { + list += activity + } + } + } + list.toList + } + } - def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] = - Activities - .join(Repositories) - .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list + def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = { + if (!ActivityLog.exists()) { + List.empty + } else { + val list = new ListBuffer[Activity] + Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => + var json: String = null + while (list.length < 50 && { json = reader.readLine(); json } != null) { + val activity = read[Activity](json) + if (owners.contains(activity.userName)) { + list += activity + } else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName) + .map(_.isPrivate) + .getOrElse(true)) { + list += activity + } + } + } + list.toList + } + } - def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)( - implicit s: Session - ): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "create_repository", - s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", - None, - currentDate + def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "create_repository", + s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } + + def recordDeleteRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "delete_repository", + s"[user:${activityUserName}] deleted [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) + ) + } + + def recordTransferRepositoryActivity( + userName: String, + repositoryName: String, + oldUserName: String, + activityUserName: String + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "transfer_repository", + s"[user:${activityUserName}] transfered [repo:${oldUserName}/${repositoryName}] to [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) + ) + } + + def recordRenameRepositoryActivity( + userName: String, + repositoryName: String, + oldRepositoryName: String, + activityUserName: String + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "rename_repository", + s"[user:${activityUserName}] renamed [repo:${userName}/${oldRepositoryName}] at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) + ) + } def recordCreateIssueActivity( userName: String, @@ -69,16 +173,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "open_issue", - s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "open_issue", + s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCloseIssueActivity( userName: String, @@ -86,16 +194,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "close_issue", - s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "close_issue", + s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordClosePullRequestActivity( userName: String, @@ -103,16 +215,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "close_issue", - s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "close_issue", + s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordReopenIssueActivity( userName: String, @@ -120,16 +236,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "reopen_issue", - s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "reopen_issue", + s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordReopenPullRequestActivity( userName: String, @@ -137,16 +257,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "reopen_issue", - s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "reopen_issue", + s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCommentIssueActivity( userName: String, @@ -154,16 +278,20 @@ activityUserName: String, issueId: Int, comment: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "comment_issue", - s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(cut(comment, 200)), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "comment_issue", + s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", + Some(cut(comment, 200)), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCommentPullRequestActivity( userName: String, @@ -171,16 +299,20 @@ activityUserName: String, issueId: Int, comment: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "comment_issue", - s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(cut(comment, 200)), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "comment_issue", + s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", + Some(cut(comment, 200)), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCommentCommitActivity( userName: String, @@ -188,32 +320,40 @@ activityUserName: String, commitId: String, comment: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "comment_commit", - s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", - Some(cut(comment, 200)), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "comment_commit", + s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", + Some(cut(comment, 200)), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCreateWikiPageActivity( userName: String, repositoryName: String, activityUserName: String, pageName: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "create_wiki", - s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", - Some(pageName), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "create_wiki", + s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", + Some(pageName), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordEditWikiPageActivity( userName: String, @@ -221,16 +361,20 @@ activityUserName: String, pageName: String, commitId: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "edit_wiki", - s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", - Some(pageName + ":" + commitId), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "edit_wiki", + s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", + Some(pageName + ":" + commitId), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordPushActivity( userName: String, @@ -238,23 +382,27 @@ activityUserName: String, branchName: String, commits: List[JGitUtil.CommitInfo] - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "push", - s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", - Some( - commits - .take(5) - .map { commit => - commit.id + ":" + commit.shortMessage - } - .mkString("\n") - ), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "push", + s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", + Some( + commits + .take(5) + .map { commit => + commit.id + ":" + commit.shortMessage + } + .mkString("\n") + ), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCreateTagActivity( userName: String, @@ -262,16 +410,20 @@ activityUserName: String, tagName: String, commits: List[JGitUtil.CommitInfo] - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "create_tag", - s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", - None, - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "create_tag", + s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } def recordDeleteTagActivity( userName: String, @@ -279,61 +431,80 @@ activityUserName: String, tagName: String, commits: List[JGitUtil.CommitInfo] - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "delete_tag", - s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", - None, - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "delete_tag", + s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } def recordCreateBranchActivity( userName: String, repositoryName: String, activityUserName: String, branchName: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "create_branch", - s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", - None, - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "create_branch", + s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } def recordDeleteBranchActivity( userName: String, repositoryName: String, activityUserName: String, branchName: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "delete_branch", - s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", - None, - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "delete_branch", + s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } - def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)( - implicit s: Session - ): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "fork", - s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", - None, - currentDate + def recordForkActivity( + userName: String, + repositoryName: String, + activityUserName: String, + forkedUserName: String + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "fork", + s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } def recordPullRequestActivity( userName: String, @@ -341,16 +512,20 @@ activityUserName: String, issueId: Int, title: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "open_pullreq", - s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "open_pullreq", + s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", + Some(title), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordMergeActivity( userName: String, @@ -358,16 +533,20 @@ activityUserName: String, issueId: Int, message: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "merge_pullreq", - s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(message), - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "merge_pullreq", + s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", + Some(message), + currentDate, + UUID.randomUUID().toString + ) ) + } def recordReleaseActivity( userName: String, @@ -375,16 +554,20 @@ activityUserName: String, releaseName: String, tagName: String - )(implicit s: Session): Unit = - Activities insert Activity( - userName, - repositoryName, - activityUserName, - "release", - s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]", - None, - currentDate + ): Unit = { + writeLog( + Activity( + userName, + repositoryName, + activityUserName, + "release", + s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]", + None, + currentDate, + UUID.randomUUID().toString + ) ) + } private def cut(value: String, length: Int): String = if (value.length > length) value.substring(0, length) + "..." else value diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 2e3d881..c04dd6c 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -1,6 +1,5 @@ package gitbucket.core.service -import gitbucket.core.api.JsonFormat import gitbucket.core.controller.Context import gitbucket.core.util._ import gitbucket.core.util.SyntaxSugars._ @@ -9,14 +8,11 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.service.WebHookService.WebHookPushPayload import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir} -import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo} +import gitbucket.core.util.JGitUtil.FileInfo import org.apache.commons.io.FileUtils import org.eclipse.jgit.api.Git -import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} import org.eclipse.jgit.lib.{Repository => _, _} -import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} import scala.util.Using trait RepositoryService { @@ -119,15 +115,6 @@ } .update(newUserName, newRepositoryName) - // Updates activity fk before deleting repository because activity is sorted by activityId - // and it can't be changed by deleting-and-inserting record. - Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity => - Activities - .filter(_.activityId === activity.activityId.bind) - .map(x => (x.userName, x.repositoryName)) - .update(newUserName, newRepositoryName) - } - deleteRepositoryOnModel(oldUserName, oldRepositoryName) RepositoryWebHooks.insertAll( @@ -213,50 +200,6 @@ collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* ) - // Update activity messages - Activities - .filter { t => - (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") - } - .map { t => - t.activityId -> t.message - } - .list - .foreach { - case (activityId, message) => - Activities - .filter(_.activityId === activityId.bind) - .map(_.message) - .update( - message - .replace( - s"[repo:${oldUserName}/${oldRepositoryName}]", - s"[repo:${newUserName}/${newRepositoryName}]" - ) - .replace( - s"[branch:${oldUserName}/${oldRepositoryName}#", - s"[branch:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[tag:${oldUserName}/${oldRepositoryName}#", - s"[tag:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[pullreq:${oldUserName}/${oldRepositoryName}#", - s"[pullreq:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[issue:${oldUserName}/${oldRepositoryName}#", - s"[issue:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[commit:${oldUserName}/${oldRepositoryName}@", - s"[commit:${newUserName}/${newRepositoryName}@" - ) - ) - } // Move git repository defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir => if (dir.isDirectory) { @@ -304,7 +247,7 @@ } private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = { - Activities.filter(_.byRepository(userName, repositoryName)).delete +// Activities.filter(_.byRepository(userName, repositoryName)).delete Collaborators.filter(_.byRepository(userName, repositoryName)).delete CommitComments.filter(_.byRepository(userName, repositoryName)).delete IssueLabels.filter(_.byRepository(userName, repositoryName)).delete diff --git a/src/main/scala/gitbucket/core/service/RequestCache.scala b/src/main/scala/gitbucket/core/service/RequestCache.scala index d1b12de..485e769 100644 --- a/src/main/scala/gitbucket/core/service/RequestCache.scala +++ b/src/main/scala/gitbucket/core/service/RequestCache.scala @@ -1,9 +1,11 @@ package gitbucket.core.service -import gitbucket.core.model.{Session, Issue, Account} +import gitbucket.core.model.{Account, Issue, Repository, Session} import gitbucket.core.util.Implicits import gitbucket.core.controller.Context import Implicits.request2Session +import gitbucket.core.model.Profile.{Accounts, Repositories} +import gitbucket.core.model.Profile.profile.blockingApi._ /** * This service is used for a view helper mainly. @@ -23,21 +25,41 @@ private implicit def context2Session(implicit context: Context): Session = request2Session(context.request) - def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = { + def getIssueFromCache(userName: String, repositoryName: String, issueId: String)( + implicit context: Context + ): Option[Issue] = { context.cache(s"issue.${userName}/${repositoryName}#${issueId}") { super.getIssue(userName, repositoryName, issueId) } } - def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = { + def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = { context.cache(s"account.${userName}") { super.getAccountByUserName(userName) } } - def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = { + def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = { context.cache(s"account.${mailAddress}") { super.getAccountByMailAddress(mailAddress) } } + + def getRepositoryInfoFromCache(userName: String, repositoryName: String)( + implicit context: Context + ): Option[Repository] = { + context.cache(s"repository.${userName}/${repositoryName}") { + Repositories + .join(Accounts) + .on(_.userName === _.userName) + .filter { + case (t1, t2) => + t1.byRepository(userName, repositoryName) && t2.removed === false.bind + } + .map { + case (t1, t2) => t1 + } + .firstOption + } + } } diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 1de4e1a..acc6f77 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -240,7 +240,8 @@ with WebHookPullRequestService with WebHookPullRequestReviewCommentService with CommitsService - with SystemSettingsService { + with SystemSettingsService + with RequestCache { private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private var existIds: Seq[String] = Nil diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala index aa85d79..ad2f6a6 100644 --- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala +++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala @@ -2,11 +2,10 @@ import java.io.{File, FileOutputStream} -import akka.event.Logging import com.typesafe.config.ConfigFactory import gitbucket.core.GitBucketCoreModule import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.service.{ActivityService, SystemSettingsService} +import gitbucket.core.service.SystemSettingsService import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.Directory._ import gitbucket.core.util.JDBCUtil._ @@ -21,8 +20,6 @@ import org.apache.commons.io.{FileUtils, IOUtils} import org.slf4j.LoggerFactory -import akka.actor.{Actor, ActorSystem, Props} -import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension import scala.jdk.CollectionConverters._ import scala.util.Using @@ -35,23 +32,23 @@ private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) - // ActorSystem for Quartz scheduler - private val system = ActorSystem( - "job", - ConfigFactory.parseString(""" - |akka { - | daemonic = on - | coordinated-shutdown.run-by-jvm-shutdown-hook = off - | quartz { - | schedules { - | Daily { - | expression = "0 0 0 * * ?" - | } - | } - | } - |} - """.stripMargin) - ) +// // ActorSystem for Quartz scheduler +// private val system = ActorSystem( +// "job", +// ConfigFactory.parseString(""" +// |akka { +// | daemonic = on +// | coordinated-shutdown.run-by-jvm-shutdown-hook = off +// | quartz { +// | schedules { +// | Daily { +// | expression = "0 0 0 * * ?" +// | } +// | } +// | } +// |} +// """.stripMargin) +// ) override def contextInitialized(event: ServletContextEvent): Unit = { val dataDir = event.getServletContext.getInitParameter("gitbucket.home") @@ -95,10 +92,10 @@ PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn) } - // Start Quartz scheduler - val scheduler = QuartzSchedulerExtension(system) - - scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity") +// // Start Quartz scheduler +// val scheduler = QuartzSchedulerExtension(system) +// +// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity") } private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = { @@ -172,8 +169,8 @@ } override def contextDestroyed(event: ServletContextEvent): Unit = { - // Shutdown Quartz scheduler - system.terminate() +// // Shutdown Quartz scheduler +// system.terminate() // Shutdown plugins PluginRegistry.shutdown(event.getServletContext, loadSystemSettings()) // Close datasource @@ -181,21 +178,3 @@ } } - -class DeleteOldActivityActor extends Actor with SystemSettingsService with ActivityService { - - private val logger = Logging(context.system, this) - - def receive = { - case s: String => { - loadSystemSettings().activityLogLimit.foreach { limit => - if (limit > 0) { - Database() withTransaction { implicit session => - val rows = deleteOldActivities(limit) - logger.info(s"Deleted ${rows} activity logs") - } - } - } - } - } -} diff --git a/src/main/scala/gitbucket/core/util/Directory.scala b/src/main/scala/gitbucket/core/util/Directory.scala index 0ba5238..6c34797 100644 --- a/src/main/scala/gitbucket/core/util/Directory.scala +++ b/src/main/scala/gitbucket/core/util/Directory.scala @@ -29,6 +29,8 @@ val GitBucketConf = new File(GitBucketHome, "gitbucket.conf") + val ActivityLog = new File(GitBucketHome, "activity.log") + val RepositoryHome = s"${GitBucketHome}/repositories" val DatabaseHome = s"${GitBucketHome}/data" diff --git a/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala b/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala index 1c233e9..3df7c86 100644 --- a/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala +++ b/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala @@ -17,7 +17,7 @@ val src = if (mailAddress.isEmpty) { // by user name - getAccountByUserName(userName).map { account => + getAccountByUserNameFromCache(userName).map { account => if (account.image.isEmpty && context.settings.gravatar) { s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { @@ -28,7 +28,7 @@ } } else { // by mail address - getAccountByMailAddress(mailAddress).map { account => + getAccountByMailAddressFromCache(mailAddress).map { account => if (account.image.isEmpty && context.settings.gravatar) { s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { diff --git a/src/main/scala/gitbucket/core/view/LinkConverter.scala b/src/main/scala/gitbucket/core/view/LinkConverter.scala index cbfac59..dbc76cc 100644 --- a/src/main/scala/gitbucket/core/view/LinkConverter.scala +++ b/src/main/scala/gitbucket/core/view/LinkConverter.scala @@ -16,7 +16,7 @@ val userName = repository.repository.userName val repositoryName = repository.repository.repositoryName - getIssue(userName, repositoryName, issueId.toString) match { + getIssueFromCache(userName, repositoryName, issueId.toString) match { case Some(issue) => s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil .escapeHtml(title)}</strong> #${issueId}</a>""" @@ -43,7 +43,7 @@ escaped // convert username/project@SHA to link .replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r) { m => - getAccountByUserName(m.group(2)).map { _ => + getAccountByUserNameFromCache(m.group(2)).map { _ => s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group( 3 )}@${m.group(4).substring(0, 7)}</a></code>""" @@ -53,7 +53,7 @@ // convert username/project#Num to link .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m => - getIssue(m.group(2), m.group(3), m.group(4)) match { + getIssueFromCache(m.group(2), m.group(3), m.group(4)) match { case Some(issue) if (issue.isPullRequest) => Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group( 3 @@ -68,7 +68,7 @@ // convert username@SHA to link .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r) { m => - getAccountByUserName(m.group(2)).map { _ => + getAccountByUserNameFromCache(m.group(2)).map { _ => s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m .group(3) .substring(0, 7)}</a></code>""" @@ -77,7 +77,7 @@ // convert username#Num to link .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m => - getIssue(m.group(2), repository.name, m.group(3)) match { + getIssueFromCache(m.group(2), repository.name, m.group(3)) match { case Some(issue) if (issue.isPullRequest) => Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m .group(3)}</a>""") @@ -92,7 +92,7 @@ // convert issue id to link .replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r) { m => val prefix = if (m.group(2) == "issue:") "#" else m.group(2) - getIssue(repository.owner, repository.name, m.group(3)) match { + getIssueFromCache(repository.owner, repository.name, m.group(3)) match { case Some(issue) if (issue.isPullRequest) => Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m .group(3)}</a>""") @@ -106,7 +106,7 @@ // convert @username to link .replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r) { m => - getAccountByUserName(m.group(2)).map { _ => + getAccountByUserNameFromCache(m.group(2)).map { _ => s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>""" } } diff --git a/src/main/scala/gitbucket/core/view/helpers.scala b/src/main/scala/gitbucket/core/view/helpers.scala index e9b1413..db35356 100644 --- a/src/main/scala/gitbucket/core/view/helpers.scala +++ b/src/main/scala/gitbucket/core/view/helpers.scala @@ -3,7 +3,6 @@ import java.text.SimpleDateFormat import java.util.{Date, Locale, TimeZone} -import com.nimbusds.jose.util.JSONObjectUtils import gitbucket.core.controller.Context import gitbucket.core.model.CommitState import gitbucket.core.model.PullRequest @@ -196,7 +195,7 @@ import scala.util.matching.Regex._ implicit class RegexReplaceString(private val s: String) extends AnyVal { - def replaceAll(pattern: String, replacer: (Match) => String): String = { + def replaceAll(pattern: String)(replacer: Match => String): String = { pattern.r.replaceAllIn(s, (m: Match) => replacer(m).replace("$", "\\$")) } } @@ -204,50 +203,64 @@ /** * Convert link notations in the activity message. */ + // format: off def activityMessage(message: String)(implicit context: Context): Html = Html( message - .replaceAll( - "\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", - s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>""" - ) - .replaceAll( - "\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", - s"""<a href="${context.path}/$$1/$$2/pull/$$3">$$1/$$2#$$3</a>""" - ) - .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""") - .replaceAll( - "\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", - (m: Match) => - s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil - .escapeHtml( - m.group(3) - )}</a>""" - ) - .replaceAll( - "\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", - (m: Match) => - s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil - .escapeHtml( - m.group(3) - )}</a>""" - ) - .replaceAll("\\[user:([^\\s]+?)\\]", (m: Match) => user(m.group(1)).body) - .replaceAll( - "\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", - (m: Match) => - s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m - .group(2)}@${m.group(3).substring(0, 7)}</a>""" - ) - .replaceAll( - "\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]", - (m: Match) => - s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil - .escapeHtml( - m.group(4) - )}</a>""" - ) + .replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/issues/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>""" + } else { + s"${m.group(1)}/${m.group(2)}#${m.group(3)}" + } + } + .replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/pull/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>""" + } else { + s"${m.group(1)}/${m.group(2)}#${m.group(3)}" + } + } + .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]") { m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}">${m.group(1)}/${m.group(2)}</a>""" + } else { + s"${m.group(1)}/${m.group(2)}" + } + } + .replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>""" + } else { + StringUtil.escapeHtml(m.group(3)) + } + } + .replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>""" + } else { + StringUtil.escapeHtml(m.group(3)) + } + } + .replaceAll("\\[user:([^\\s]+?)\\]") { m => + user(m.group(1)).body + } + .replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]") { m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}</a>""" + } else { + s"${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}" + } + } + .replaceAll("\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]") { m => + if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { + s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(4))}</a>""" + } else { + StringUtil.escapeHtml(m.group(4)) + } + } ) + // format: off /** * Remove html tags from the given Html instance. @@ -333,9 +346,9 @@ content: Html )(implicit context: Context): Html = (if (mailAddress.isEmpty) { - getAccountByUserName(userName) + getAccountByUserNameFromCache(userName) } else { - getAccountByMailAddress(mailAddress) + getAccountByMailAddressFromCache(mailAddress) }).map { account => Html(s"""<a href="${url(account.userName)}" class="${styleClass}">${content}</a>""") } getOrElse content diff --git a/src/main/twirl/gitbucket/core/helper/activities.scala.html b/src/main/twirl/gitbucket/core/helper/activities.scala.html index 306a539..8a9dfdf 100644 --- a/src/main/twirl/gitbucket/core/helper/activities.scala.html +++ b/src/main/twirl/gitbucket/core/helper/activities.scala.html @@ -7,21 +7,24 @@ @activities.map { activity => <div class="block"> @(activity.activityType match { - case "open_issue" => detailActivity(activity, "issue-opened") - case "comment_issue" => detailActivity(activity, "comment-discussion") - case "comment_commit" => detailActivity(activity, "comment-discussion") - case "close_issue" => detailActivity(activity, "issue-closed") - case "reopen_issue" => detailActivity(activity, "issue-reopened") - case "open_pullreq" => detailActivity(activity, "git-pull-request") - case "merge_pullreq" => detailActivity(activity, "git-merge") - case "release" => detailActivity(activity, "package") - case "create_repository" => simpleActivity(activity, "repo") - case "create_branch" => simpleActivity(activity, "git-branch") - case "delete_branch" => simpleActivity(activity, "circle-slash") - case "create_tag" => simpleActivity(activity, "tag") - case "delete_tag" => simpleActivity(activity, "circle-slash") - case "fork" => simpleActivity(activity, "repo-forked") - case "push" => customActivity(activity, "git-commit"){ + case "open_issue" => simpleActivity(activity) + case "comment_issue" => simpleActivity(activity) + case "comment_commit" => simpleActivity(activity) + case "close_issue" => simpleActivity(activity) + case "reopen_issue" => simpleActivity(activity) + case "open_pullreq" => simpleActivity(activity) + case "merge_pullreq" => simpleActivity(activity) + case "release" => simpleActivity(activity) + case "create_repository" => simpleActivity(activity) + case "delete_repository" => simpleActivity(activity) + case "rename_repository" => simpleActivity(activity) + case "transfer_repository" => simpleActivity(activity) + case "create_branch" => simpleActivity(activity) + case "delete_branch" => simpleActivity(activity) + case "create_tag" => simpleActivity(activity) + case "delete_tag" => simpleActivity(activity) + case "fork" => simpleActivity(activity) + case "push" => customActivity(activity){ <div class="small activity-message"> {activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) => if(i == 3){ @@ -37,12 +40,12 @@ }} </div> } - case "create_wiki" => customActivity(activity, "book"){ + case "create_wiki" => customActivity(activity){ <div class="small activity-message"> Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>. </div> } - case "edit_wiki" => customActivity(activity, "book"){ + case "edit_wiki" => customActivity(activity){ activity.additionalInfo.get.split(":") match { case Array(pageName, commitId) => <div class="small activity-message"> @@ -60,26 +63,7 @@ } } -@detailActivity(activity: gitbucket.core.model.Activity, image: String) = { - @* - <div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div> - *@ - <div> - <div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div> - <div class="strong"> - @helpers.avatarLink(activity.activityUserName, 16) - @helpers.activityMessage(activity.message) - </div> - @activity.additionalInfo.map { additionalInfo => - <div class=" activity-message">@additionalInfo</div> - } - </div> -} - -@customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = { - @* - <div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div> - *@ +@customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = { <div> <div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div> <div class="strong"> @@ -90,10 +74,7 @@ </div> } -@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = { - @* - <div class="activity-icon-small"><i class="octicon octicon-@image"></i></div> - *@ +@simpleActivity(activity: gitbucket.core.model.Activity) = { <div> <span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span> <div> diff --git a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala index 67f4bbc..f1daec7 100644 --- a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala @@ -14,7 +14,7 @@ class MergeServiceSpec extends FunSpec { val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService - with WebHookPullRequestService with WebHookPullRequestReviewCommentService {} + with WebHookPullRequestService with WebHookPullRequestReviewCommentService with RequestCache {} val branch = "master" val issueId = 10 def initRepository(owner: String, name: String): File = { diff --git a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala index 9cd3c20..6e7e946 100644 --- a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala @@ -18,7 +18,8 @@ with PrioritiesService with WebHookService with WebHookPullRequestService - with WebHookPullRequestReviewCommentService { + with WebHookPullRequestReviewCommentService + with RequestCache { def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala index 4c53b32..3148ed8 100644 --- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala +++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala @@ -99,7 +99,7 @@ lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService - with WebHookPullRequestReviewCommentService { + with WebHookPullRequestReviewCommentService with RequestCache { override def fetchAsPullRequest( userName: String, repositoryName: String, diff --git a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala index 744b7ac..6bbb8e0 100644 --- a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala @@ -7,7 +7,7 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase { lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService with MergeService with PullRequestService with IssuesService with CommitsService with LabelsService - with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService + with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService with RequestCache test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { withTestDB { implicit session => diff --git a/src/test/scala/gitbucket/core/view/AvatarImageProviderSpec.scala b/src/test/scala/gitbucket/core/view/AvatarImageProviderSpec.scala index 2004bed..fdf03dc 100644 --- a/src/test/scala/gitbucket/core/view/AvatarImageProviderSpec.scala +++ b/src/test/scala/gitbucket/core/view/AvatarImageProviderSpec.scala @@ -169,8 +169,9 @@ context: Context ): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip) - override def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = account - override def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = account + override def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = + account + override def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = account } }