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 model.Account
|
||||
import service.{SystemSettingsService, AccountService}
|
||||
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
|
||||
import java.text.SimpleDateFormat
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||
import org.scalatra.i18n._
|
||||
|
||||
@@ -164,7 +163,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
||||
/**
|
||||
* Base trait for controllers which manages account information.
|
||||
*/
|
||||
trait AccountManagementControllerBase extends ControllerBase with FileUploadControllerBase {
|
||||
trait AccountManagementControllerBase extends ControllerBase {
|
||||
self: AccountService =>
|
||||
|
||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||
@@ -175,9 +174,9 @@ trait AccountManagementControllerBase extends ControllerBase with FileUploadCont
|
||||
}
|
||||
} else {
|
||||
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(
|
||||
getTemporaryFile(fileId),
|
||||
new java.io.File(getTemporaryDir(session.getId), fileId),
|
||||
new java.io.File(getUserUploadDir(userName), 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
|
||||
|
||||
import _root_.util.{Keys, FileUtil}
|
||||
import util.{Keys, FileUtil}
|
||||
import util.ControlUtil._
|
||||
import util.Directory._
|
||||
import org.scalatra._
|
||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
|
||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
/**
|
||||
* Provides Ajax based file upload functionality.
|
||||
*
|
||||
* This servlet saves uploaded file as temporary file and returns the unique id.
|
||||
* You can get uploaded file using [[app.FileUploadControllerBase#getTemporaryFile()]] with this id.
|
||||
* This servlet saves uploaded file.
|
||||
*/
|
||||
class FileUploadController extends ScalatraServlet with FileUploadSupport with FileUploadControllerBase {
|
||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
||||
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||
|
||||
post("/image"){
|
||||
fileParams.get("file") match {
|
||||
case Some(file) if(FileUtil.isImage(file.name)) => defining(generateFileId){ fileId =>
|
||||
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
|
||||
execute { (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
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)
|
||||
}
|
||||
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 milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package servlet
|
||||
|
||||
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.
|
||||
*/
|
||||
class SessionCleanupListener extends HttpSessionListener with FileUploadControllerBase {
|
||||
class SessionCleanupListener extends HttpSessionListener {
|
||||
|
||||
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"
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
def getRepositoryDir(owner: String, repository: String): File =
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils
|
||||
import java.net.URLConnection
|
||||
import java.io.File
|
||||
import util.ControlUtil._
|
||||
import scala.util.Random
|
||||
|
||||
object FileUtil {
|
||||
|
||||
@@ -31,9 +32,7 @@ object FileUtil {
|
||||
|
||||
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
||||
|
||||
def getFileName(path: String): String = defining(path.lastIndexOf('/')){ i =>
|
||||
if(i >= 0) path.substring(i + 1) else path
|
||||
}
|
||||
def generateFileId: String = System.currentTimeMillis + Random.alphanumeric.take(10).mkString
|
||||
|
||||
def getExtension(name: String): String =
|
||||
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">
|
||||
<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 class="pull-right">
|
||||
<a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Syntax Guide</a>
|
||||
</li>
|
||||
*@
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tab1">
|
||||
<span id="error-content" class="error"></span>
|
||||
@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 class="tab-pane" id="tab2">
|
||||
<div class="markdown-body" id="preview-area">
|
||||
|
||||
@@ -21,7 +21,6 @@ $(function(){
|
||||
var dropzone = new Dropzone('div#clickable', {
|
||||
url: '@path/upload/image',
|
||||
previewsContainer: 'div#avatar',
|
||||
paramName: 'file',
|
||||
parallelUploads: 1,
|
||||
thumbnailWidth: 120,
|
||||
thumbnailHeight: 120
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@(content: String, commentId: Int, owner: String, repository: String)(implicit context: app.Context)
|
||||
@import context._
|
||||
<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>
|
||||
<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"/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
@import context._
|
||||
<span id="error-edit-title" class="error"></span>
|
||||
<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>
|
||||
<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"/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user