mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-06 13:35:50 +01:00
Merge branch 'fileupload'
This commit is contained in:
@@ -11,8 +11,7 @@ import jp.sf.amateras.scalatra.forms._
|
|||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import model.Account
|
import model.Account
|
||||||
import service.{SystemSettingsService, AccountService}
|
import service.{SystemSettingsService, AccountService}
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
|
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||||
import org.scalatra.i18n._
|
import org.scalatra.i18n._
|
||||||
|
|
||||||
@@ -164,7 +163,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
|||||||
/**
|
/**
|
||||||
* Base trait for controllers which manages account information.
|
* Base trait for controllers which manages account information.
|
||||||
*/
|
*/
|
||||||
trait AccountManagementControllerBase extends ControllerBase with FileUploadControllerBase {
|
trait AccountManagementControllerBase extends ControllerBase {
|
||||||
self: AccountService =>
|
self: AccountService =>
|
||||||
|
|
||||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||||
@@ -175,9 +174,9 @@ trait AccountManagementControllerBase extends ControllerBase with FileUploadCont
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileId.map { fileId =>
|
fileId.map { fileId =>
|
||||||
val filename = "avatar." + FileUtil.getExtension(getUploadedFilename(fileId).get)
|
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||||
FileUtils.moveFile(
|
FileUtils.moveFile(
|
||||||
getTemporaryFile(fileId),
|
new java.io.File(getTemporaryDir(session.getId), fileId),
|
||||||
new java.io.File(getUserUploadDir(userName), filename)
|
new java.io.File(getUserUploadDir(userName), filename)
|
||||||
)
|
)
|
||||||
updateAvatarImage(userName, Some(filename))
|
updateAvatarImage(userName, Some(filename))
|
||||||
@@ -197,28 +196,3 @@ trait AccountManagementControllerBase extends ControllerBase with FileUploadCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Base trait for controllers which needs file uploading feature.
|
|
||||||
*/
|
|
||||||
trait FileUploadControllerBase {
|
|
||||||
|
|
||||||
def generateFileId: String =
|
|
||||||
new SimpleDateFormat("yyyyMMddHHmmSSsss").format(new java.util.Date(System.currentTimeMillis))
|
|
||||||
|
|
||||||
def TemporaryDir(implicit session: HttpSession): java.io.File =
|
|
||||||
new java.io.File(GitBucketHome, s"tmp/_upload/${session.getId}")
|
|
||||||
|
|
||||||
def getTemporaryFile(fileId: String)(implicit session: HttpSession): java.io.File =
|
|
||||||
new java.io.File(TemporaryDir, fileId)
|
|
||||||
|
|
||||||
// def removeTemporaryFile(fileId: String)(implicit session: HttpSession): Unit =
|
|
||||||
// getTemporaryFile(fileId).delete()
|
|
||||||
|
|
||||||
def removeTemporaryFiles()(implicit session: HttpSession): Unit =
|
|
||||||
FileUtils.deleteDirectory(TemporaryDir)
|
|
||||||
|
|
||||||
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] =
|
|
||||||
session.getAndRemove[String](Keys.Session.Upload(fileId))
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,42 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import _root_.util.{Keys, FileUtil}
|
import util.{Keys, FileUtil}
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
|
import util.Directory._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
|
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Ajax based file upload functionality.
|
* Provides Ajax based file upload functionality.
|
||||||
*
|
*
|
||||||
* This servlet saves uploaded file as temporary file and returns the unique id.
|
* This servlet saves uploaded file.
|
||||||
* You can get uploaded file using [[app.FileUploadControllerBase#getTemporaryFile()]] with this id.
|
|
||||||
*/
|
*/
|
||||||
class FileUploadController extends ScalatraServlet with FileUploadSupport with FileUploadControllerBase {
|
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||||
|
|
||||||
post("/image"){
|
post("/image"){
|
||||||
fileParams.get("file") match {
|
execute { (file, fileId) =>
|
||||||
case Some(file) if(FileUtil.isImage(file.name)) => defining(generateFileId){ fileId =>
|
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||||
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
|
|
||||||
session += Keys.Session.Upload(fileId) -> file.name
|
session += Keys.Session.Upload(fileId) -> file.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/image/:owner/:repository"){
|
||||||
|
execute { (file, fileId) =>
|
||||||
|
FileUtils.writeByteArrayToFile(new java.io.File(getAttachedDir(params("owner"), params("repository")), fileId), file.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
|
||||||
|
case Some(file) if(FileUtil.isImage(file.name)) =>
|
||||||
|
defining(FileUtil.generateFileId){ fileId =>
|
||||||
|
f(file, fileId)
|
||||||
|
|
||||||
Ok(fileId)
|
Ok(fileId)
|
||||||
}
|
}
|
||||||
case None => BadRequest
|
case _ => BadRequest
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,6 +273,12 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
|
||||||
|
defining(new java.io.File(Directory.getAttachedDir(repository.owner, repository.name), params("file"))){ file =>
|
||||||
|
if(file.exists) file else NotFound
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package servlet
|
package servlet
|
||||||
|
|
||||||
import javax.servlet.http.{HttpSessionEvent, HttpSessionListener}
|
import javax.servlet.http.{HttpSessionEvent, HttpSessionListener}
|
||||||
import app.FileUploadControllerBase
|
import org.apache.commons.io.FileUtils
|
||||||
|
import util.Directory._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes session associated temporary files when session is destroyed.
|
* Removes session associated temporary files when session is destroyed.
|
||||||
*/
|
*/
|
||||||
class SessionCleanupListener extends HttpSessionListener with FileUploadControllerBase {
|
class SessionCleanupListener extends HttpSessionListener {
|
||||||
|
|
||||||
def sessionCreated(se: HttpSessionEvent): Unit = {}
|
def sessionCreated(se: HttpSessionEvent): Unit = {}
|
||||||
|
|
||||||
def sessionDestroyed(se: HttpSessionEvent): Unit = removeTemporaryFiles()(se.getSession)
|
def sessionDestroyed(se: HttpSessionEvent): Unit = FileUtils.deleteDirectory(getTemporaryDir(se.getSession.getId))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,31 +34,29 @@ object Directory {
|
|||||||
|
|
||||||
val DatabaseHome = s"${GitBucketHome}/data"
|
val DatabaseHome = s"${GitBucketHome}/data"
|
||||||
|
|
||||||
/**
|
|
||||||
* Repository names of the specified user.
|
|
||||||
*/
|
|
||||||
def getRepositories(owner: String): List[String] =
|
|
||||||
defining(new File(s"${RepositoryHome}/${owner}")){ dir =>
|
|
||||||
if(dir.exists){
|
|
||||||
dir.listFiles.filter { file =>
|
|
||||||
file.isDirectory && !file.getName.endsWith(".wiki.git")
|
|
||||||
}.map(_.getName.replaceFirst("\\.git$", "")).toList
|
|
||||||
} else {
|
|
||||||
Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Substance directory of the repository.
|
* Substance directory of the repository.
|
||||||
*/
|
*/
|
||||||
def getRepositoryDir(owner: String, repository: String): File =
|
def getRepositoryDir(owner: String, repository: String): File =
|
||||||
new File(s"${RepositoryHome}/${owner}/${repository}.git")
|
new File(s"${RepositoryHome}/${owner}/${repository}.git")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory for files which are attached to issue.
|
||||||
|
*/
|
||||||
|
def getAttachedDir(owner: String, repository: String): File =
|
||||||
|
new File(s"${RepositoryHome}/${owner}/${repository}/issues")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory for uploaded files by the specified user.
|
* Directory for uploaded files by the specified user.
|
||||||
*/
|
*/
|
||||||
def getUserUploadDir(userName: String): File = new File(s"${GitBucketHome}/data/${userName}/files")
|
def getUserUploadDir(userName: String): File = new File(s"${GitBucketHome}/data/${userName}/files")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root of temporary directories for the upload file.
|
||||||
|
*/
|
||||||
|
def getTemporaryDir(sessionId: String): File =
|
||||||
|
new File(s"${GitBucketHome}/tmp/_upload/${sessionId}")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root of temporary directories for the specified repository.
|
* Root of temporary directories for the specified repository.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils
|
|||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
|
|
||||||
@@ -31,9 +32,7 @@ object FileUtil {
|
|||||||
|
|
||||||
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
||||||
|
|
||||||
def getFileName(path: String): String = defining(path.lastIndexOf('/')){ i =>
|
def generateFileId: String = System.currentTimeMillis + Random.alphanumeric.take(10).mkString
|
||||||
if(i >= 0) path.substring(i + 1) else path
|
|
||||||
}
|
|
||||||
|
|
||||||
def getExtension(name: String): String =
|
def getExtension(name: String): String =
|
||||||
name.lastIndexOf('.') match {
|
name.lastIndexOf('.') match {
|
||||||
|
|||||||
24
src/main/twirl/helper/attached.scala.html
Normal file
24
src/main/twirl/helper/attached.scala.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@(owner: String, repository: String)(textarea: Html)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
<div class="muted">
|
||||||
|
@textarea
|
||||||
|
Attach images by dragging & dropping, or selecting them.
|
||||||
|
</div>
|
||||||
|
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId =>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#@textareaId').closest('div').dropzone({
|
||||||
|
url: '@path/upload/image/@owner/@repository',
|
||||||
|
maxFilesize: 10,
|
||||||
|
acceptedFiles: 'image/*',
|
||||||
|
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.',
|
||||||
|
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your images...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||||
|
success: function(file, id) {
|
||||||
|
var images = '\n![' + file.name.split('.')[0] + '](@baseURL/@owner/@repository/_attached/' + id + ')';
|
||||||
|
$('#@textareaId').val($('#@textareaId').val() + images);
|
||||||
|
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@@ -6,16 +6,18 @@
|
|||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active"><a href="#tab1" data-toggle="tab">Write</a></li>
|
<li class="active"><a href="#tab1" data-toggle="tab">Write</a></li>
|
||||||
<li><a href="#tab2" data-toggle="tab" id="preview">Preview</a></li>
|
<li><a href="#tab2" data-toggle="tab" id="preview">Preview</a></li>
|
||||||
@*
|
|
||||||
<li class="pull-right">
|
|
||||||
<a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Syntax Guide</a>
|
|
||||||
</li>
|
|
||||||
*@
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="tab1">
|
<div class="tab-pane active" id="tab1">
|
||||||
<span id="error-content" class="error"></span>
|
<span id="error-content" class="error"></span>
|
||||||
|
@textarea = {
|
||||||
<textarea id="content" name="content"@if(style.nonEmpty){ style="@style"} placeholder="@placeholder">@content</textarea>
|
<textarea id="content" name="content"@if(style.nonEmpty){ style="@style"} placeholder="@placeholder">@content</textarea>
|
||||||
|
}
|
||||||
|
@if(enableWikiLink){
|
||||||
|
@textarea
|
||||||
|
} else {
|
||||||
|
@helper.html.attached(repository.owner, repository.name)(textarea)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="tab2">
|
<div class="tab-pane" id="tab2">
|
||||||
<div class="markdown-body" id="preview-area">
|
<div class="markdown-body" id="preview-area">
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ $(function(){
|
|||||||
var dropzone = new Dropzone('div#clickable', {
|
var dropzone = new Dropzone('div#clickable', {
|
||||||
url: '@path/upload/image',
|
url: '@path/upload/image',
|
||||||
previewsContainer: 'div#avatar',
|
previewsContainer: 'div#avatar',
|
||||||
paramName: 'file',
|
|
||||||
parallelUploads: 1,
|
parallelUploads: 1,
|
||||||
thumbnailWidth: 120,
|
thumbnailWidth: 120,
|
||||||
thumbnailHeight: 120
|
thumbnailHeight: 120
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@(content: String, commentId: Int, owner: String, repository: String)(implicit context: app.Context)
|
@(content: String, commentId: Int, owner: String, repository: String)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
<span id="error-edit-content-@commentId" class="error"></span>
|
<span id="error-edit-content-@commentId" class="error"></span>
|
||||||
<textarea style="width: 680px; height: 100px;" id="edit-content-@commentId">@content</textarea>
|
@helper.html.attached(owner, repository){
|
||||||
|
<textarea style="width: 680px; height: 100px;" id="edit-content-@commentId">@content</textarea>
|
||||||
|
}
|
||||||
<div>
|
<div>
|
||||||
<input type="button" id="update-comment-@commentId" class="btn btn-small" value="Update Comment"/>
|
<input type="button" id="update-comment-@commentId" class="btn btn-small" value="Update Comment"/>
|
||||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
@import context._
|
@import context._
|
||||||
<span id="error-edit-title" class="error"></span>
|
<span id="error-edit-title" class="error"></span>
|
||||||
<input type="text" style="width: 680px;" id="edit-title" value="@title"/>
|
<input type="text" style="width: 680px;" id="edit-title" value="@title"/>
|
||||||
<textarea style="width: 680px; height: 100px; max-height: 300px;" id="edit-content">@content.getOrElse("")</textarea>
|
@helper.html.attached(owner, repository){
|
||||||
|
<textarea style="width: 680px; height: 100px; max-height: 300px;" id="edit-content">@content.getOrElse("")</textarea>
|
||||||
|
}
|
||||||
<div>
|
<div>
|
||||||
<input type="button" id="update" class="btn btn-small" value="Update Issue"/>
|
<input type="button" id="update" class="btn btn-small" value="Update Issue"/>
|
||||||
<input type="button" id="cancel" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
<input type="button" id="cancel" class="btn btn-small btn-danger pull-right" value="Cancel"/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user