diff --git a/project/build.scala b/project/build.scala index 273c0ba..d04332a 100644 --- a/project/build.scala +++ b/project/build.scala @@ -64,7 +64,9 @@ "junit" % "junit" % "4.12" % "test", "com.mchange" % "c3p0" % "0.9.5", "com.typesafe" % "config" % "1.2.1", - "com.typesafe.play" %% "twirl-compiler" % "1.0.4" + "com.typesafe.play" %% "twirl-compiler" % "1.0.4", + "com.typesafe.akka" %% "akka-actor" % "2.3.10", + "com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" ), play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._", EclipseKeys.withSource := true, diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 6c558ba..1c1271d 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -21,6 +21,7 @@ "isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())), "gravatar" -> trim(label("Gravatar", boolean())), "notification" -> trim(label("Notification", boolean())), + "activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))), "ssh" -> trim(label("SSH access", boolean())), "sshPort" -> trim(label("SSH port", optional(number()))), "smtp" -> optionalIfNotChecked("notification", mapping( diff --git a/src/main/scala/gitbucket/core/service/ActivityService.scala b/src/main/scala/gitbucket/core/service/ActivityService.scala index be9205f..6825748 100644 --- a/src/main/scala/gitbucket/core/service/ActivityService.scala +++ b/src/main/scala/gitbucket/core/service/ActivityService.scala @@ -7,6 +7,12 @@ trait ActivityService { + 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 + } + def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = Activities .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index 6a3b8d3..af4a6ef 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -19,6 +19,7 @@ props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString) props.setProperty(Gravatar, settings.gravatar.toString) props.setProperty(Notification, settings.notification.toString) + settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString)) props.setProperty(Ssh, settings.ssh.toString) settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) if(settings.notification) { @@ -65,12 +66,13 @@ } SystemSettings( getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), - getOptionValue[String](props, Information, None), + getOptionValue(props, Information, None), getValue(props, AllowAccountRegistration, false), getValue(props, AllowAnonymousAccess, true), getValue(props, IsCreateRepoOptionPublic, true), getValue(props, Gravatar, true), getValue(props, Notification, false), + getOptionValue[Int](props, ActivityLogLimit, None), getValue(props, Ssh, false), getOptionValue(props, SshPort, Some(DefaultSshPort)), if(getValue(props, Notification, false)){ @@ -120,6 +122,7 @@ isCreateRepoOptionPublic: Boolean, gravatar: Boolean, notification: Boolean, + activityLogLimit: Option[Int], ssh: Boolean, sshPort: Option[Int], smtp: Option[Smtp], @@ -166,6 +169,7 @@ private val IsCreateRepoOptionPublic = "is_create_repository_option_public" private val Gravatar = "gravatar" private val Notification = "notification" + private val ActivityLogLimit = "activity_log_limit" private val Ssh = "ssh" private val SshPort = "ssh.port" private val SmtpHost = "smtp.host" diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala index 19ea5b9..a375b37 100644 --- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala +++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala @@ -1,21 +1,22 @@ package gitbucket.core.servlet -import java.sql.{DriverManager, Connection} +import akka.event.Logging +import com.typesafe.config.ConfigFactory import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.service.SystemSettingsService -import gitbucket.core.util._ +import gitbucket.core.service.{ActivityService, SystemSettingsService} import org.apache.commons.io.FileUtils import javax.servlet.{ServletContextListener, ServletContextEvent} import org.slf4j.LoggerFactory -import ControlUtil._ import gitbucket.core.util.Versions +import akka.actor.{Actor, Props, ActorSystem} +import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension +import AutoUpdate._ /** * Initialize GitBucket system. * Update database schema and load plug-ins automatically in the context initializing. */ class InitializeListener extends ServletContextListener with SystemSettingsService { - import AutoUpdate._ private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) @@ -26,17 +27,37 @@ } org.h2.Driver.load() - using(getConnection()){ conn => + Database() withTransaction { session => + val conn = session.conn + // Migration logger.debug("Start schema update") Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn => FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") } + // Load plugins logger.debug("Initialize plugins") PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn) } + // Start Quartz scheduler + val system = ActorSystem("job", ConfigFactory.parseString( + """ + |akka { + | quartz { + | schedules { + | Daily { + | expression = "0 0 0 * * ?" + | } + | } + | } + |} + """.stripMargin)) + + val scheduler = QuartzSchedulerExtension(system) + + scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity") } override def contextDestroyed(event: ServletContextEvent): Unit = { @@ -46,9 +67,22 @@ Database.closeDataSource() } - private def getConnection(): Connection = - DriverManager.getConnection( - DatabaseConfig.url, - DatabaseConfig.user, - DatabaseConfig.password) } + +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") + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/twirl/gitbucket/core/admin/system.scala.html b/src/main/twirl/gitbucket/core/admin/system.scala.html index 47e0221..6bb5caa 100644 --- a/src/main/twirl/gitbucket/core/admin/system.scala.html +++ b/src/main/twirl/gitbucket/core/admin/system.scala.html @@ -81,6 +81,15 @@ + + +