diff --git a/src/main/scala/org/aphreet/c3/lib/FileUpload.scala b/src/main/scala/org/aphreet/c3/lib/FileUpload.scala index 12903a2..6077747 100755 --- a/src/main/scala/org/aphreet/c3/lib/FileUpload.scala +++ b/src/main/scala/org/aphreet/c3/lib/FileUpload.scala @@ -39,7 +39,7 @@ import org.aphreet.c3.lib.DependencyFactory._ import com.ifunsoftware.c3.access.{C3AccessException, DataStream, C3System} import com.ifunsoftware.c3.access.C3System._ import org.aphreet.c3.lib.metadata.Metadata._ -import org.aphreet.c3.util.C3Loggable +import org.aphreet.c3.util.{C3Exception, C3Loggable} import org.aphreet.c3.model.{Group, User} import net.liftweb.util.Helpers import Helpers._ @@ -51,7 +51,7 @@ import org.aphreet.c3.service.journal.EventType import org.aphreet.c3.util.helpers.FileTransferHelper // for ajax file upload -object FileUpload extends RestHelper with C3Loggable{ +object FileUpload extends RestHelper with C3Loggable { val c3 = inject[C3System].openOrThrowException("Cannot wire a C3 System") private val RegexGroupId = """.*groups/([^/]*)/.*""".r @@ -59,38 +59,48 @@ object FileUpload extends RestHelper with C3Loggable{ serve { case "upload" :: "file" :: currentPath Post req => { - withCurrentUserAndGroupCtx(req, currentPath){ + withCurrentUserAndGroupCtx(req, currentPath) { - val uploads = req.uploadedFiles - logger.info("Uploaded files: " + uploads) + val uploads = req.uploadedFiles + logger.info("Uploaded files: " + uploads) - val filePath = c3FilePath(currentPath) + val filePath = c3FilePath(currentPath) - logger.info("Path to upload: " + filePath) - userGroupIds: UserGroupIds => { + logger.info("Path to upload: " + filePath) + userGroupIds: UserGroupIds => { - try{ + try { val ojv: List[JObject] = uploads.map { fph => val url = removeTrailingIndex(currentPath).mkString("/", "/", "/") + fph.fileName - val description = req.param(s"description_${fph.fileName}") match {case Full(temp) => temp; case _ => ""} + val description = req.param(s"description_${fph.fileName}") match { + case Full(temp) => temp; + case _ => "" + } + + val tags = req.param(s"tags_${fph.fileName}") match { + case Full(temp) => temp; + case _ => "" + } + + if (description == "") { throw new IllegalArgumentException("Description is empty") } + if (tags == "") { throw new Exception("Tags are empty") } - val tags = req.param(s"tags_${fph.fileName}") match {case Full(temp) => temp; case _ => ""} val fileMetadata: Map[String, String] = - Map((OWNER_ID_META -> userGroupIds.userId), (GROUP_ID_META -> userGroupIds.groupId),(DESCRIPTION_META -> description),(TAGS_META -> tags)) - //req param("metadata") map(s => Map((TAGS_META -> s))) openOr Map() + Map((OWNER_ID_META -> userGroupIds.userId), (GROUP_ID_META -> userGroupIds.groupId), (DESCRIPTION_META -> description), (TAGS_META -> tags)) + //req param("metadata") map(s => Map((TAGS_META -> s))) openOr Map() + uploadToC3(fph, filePath, fileMetadata) + ("name" -> fph.fileName) ~ + ("url" -> url) ~ + ("sizef" -> fph.length) ~ + ("delete_url" -> ("/delete/file" + url)) ~ + ("delete_type" -> "DELETE") + } - uploadToC3(fph, filePath, fileMetadata) - ("name" -> fph.fileName) ~ - ("url" -> url) ~ - ("sizef" -> fph.length) ~ - ("delete_url" -> ("/delete/file" + url)) ~ - ("delete_type" -> "DELETE") - } + val jr = JsonResponse(ojv).toResponse.asInstanceOf[InMemoryResponse] + InMemoryResponse(jr.data, ("Content-Length", jr.data.length.toString) :: + ("Content-Type", "text/plain") :: S.getHeaders(Nil), + S.responseCookies, 200) - val jr = JsonResponse(ojv).toResponse.asInstanceOf[InMemoryResponse] - InMemoryResponse(jr.data, ("Content-Length", jr.data.length.toString) :: - ("Content-Type", "text/plain") :: S.getHeaders(Nil), - S.responseCookies, 200) } catch { case e: C3AccessException => { if (e.message.endsWith("already exists")) // that's ugly, need a proper cause from access api @@ -98,12 +108,18 @@ object FileUpload extends RestHelper with C3Loggable{ else BadResponse() } + case e: IllegalArgumentException => { + if (e.getMessage().endsWith("empty")) + errorResponse(uploads, 404, e.getMessage()) + else + BadResponse() + } } } } } case "replace" :: "file" :: currentPath Post req => { - withCurrentUserAndGroupCtx(req, currentPath){ + withCurrentUserAndGroupCtx(req, currentPath) { userGroupIds: UserGroupIds => { val uploads = req.uploadedFiles @@ -114,24 +130,25 @@ object FileUpload extends RestHelper with C3Loggable{ val oldFile = c3.getFile(oldFilePath.mkString("/", "/", "")) logger.info("Path to upload: " + filePath) - try{ - val ojv: List[JObject] = uploads.map { fph => - val url = removeTrailingIndex(currentPath).mkString("/", "/", "/") + fph.fileName - val fileMetadata: Map[String, String] = - Map((OWNER_ID_META -> userGroupIds.userId), (GROUP_ID_META -> userGroupIds.groupId),(DESCRIPTION_META -> oldFile.metadata.get(DESCRIPTION_META).getOrElse("")), - (TAGS_META -> oldFile.metadata.get(TAGS_META).getOrElse(""))) - val group: Box[Group] = Group.findById(filePath.head) - val relativeFilePathString = filePath.drop(2) mkString "/" - FileTransferHelper.moveToTrashCan(oldFile.name, group.open_!, "/"+relativeFilePathString, true) - uploadToC3(fph, filePath.dropRight(1), fileMetadata) - ("name" -> fph.fileName) ~ - ("url" -> url) ~ - ("sizef" -> fph.length) ~ - ("delete_url" -> ("/delete/file" + url)) ~ - ("delete_type" -> "DELETE") - } + try { + val ojv: List[JObject] = uploads.map { fph => + val url = removeTrailingIndex(currentPath).mkString("/", "/", "/") + fph.fileName + val fileMetadata: Map[String, String] = + Map((OWNER_ID_META -> userGroupIds.userId), (GROUP_ID_META -> userGroupIds.groupId), (DESCRIPTION_META -> oldFile.metadata.get(DESCRIPTION_META).getOrElse("")), + (TAGS_META -> oldFile.metadata.get(TAGS_META).getOrElse(""))) + val group = Group.findById(filePath.head).open_! + val relativeFilePathString = filePath.drop(2) mkString "/" + if (group.getFile(group.trashCanDirectory).openOr(null) == null) createTrashCan(group); + FileTransferHelper.moveToTrashCan(oldFile.name, group, "/" + relativeFilePathString, true) + uploadToC3(fph, filePath.dropRight(1), fileMetadata) + ("name" -> fph.fileName) ~ + ("url" -> url) ~ + ("sizef" -> fph.length) ~ + ("delete_url" -> ("/delete/file" + url)) ~ + ("delete_type" -> "DELETE") + } - val jr = JsonResponse(ojv).toResponse.asInstanceOf[InMemoryResponse] + val jr = JsonResponse(ojv).toResponse.asInstanceOf[InMemoryResponse] InMemoryResponse(jr.data, ("Content-Length", jr.data.length.toString) :: ("Content-Type", "text/plain") :: S.getHeaders(Nil), S.responseCookies, 200) @@ -143,11 +160,11 @@ object FileUpload extends RestHelper with C3Loggable{ BadResponse() } } - } + } } } case "delete" :: "file" :: currentPath Delete req => { - withCurrentUserAndGroupCtx(req, currentPath){ + withCurrentUserAndGroupCtx(req, currentPath) { userGroupIds: UserGroupIds => { val filePath = c3FilePath(currentPath) logger.info("File to be deleted: " + removeTrailingIndex(currentPath).mkString("/", "/", "")) @@ -161,6 +178,28 @@ object FileUpload extends RestHelper with C3Loggable{ case class UserGroupIds(userId: String, groupId: String) + private def createTrashCan(group: Group): Unit = { + val root = c3.getFile("/").asDirectory + root.getChild(group.getId) match { + case Some(node) => + val dir = node.asDirectory + dir.getChild("files") match { + case Some(node) => + val files = node.asDirectory + val metadata = Map(OWNER_ID_META -> dir.metadata.get(OWNER_ID_META).getOrElse(User.id.is.toString), + GROUP_ID_META -> group.getId, + TAGS_META -> "Trash", + DESCRIPTION_META -> "", + ACL_META -> dir.metadata.get(ACL_META).getOrElse("")) + files.createDirectory(group.trashCanName, metadata) + case None => throw new C3Exception("Failed to create trash can for group " + group.getId) + } + case None => throw new C3Exception("Can't find group with id " + group.getId) + } + } + + + private def withCurrentUserAndGroupCtx(req: Req, currentPath: List[String])(block: UserGroupIds => LiftResponse): LiftResponse = { val currentUser = User.currentUser val groupIdOpt = extractGroupId(currentPath) diff --git a/src/main/scala/org/aphreet/c3/lib/metadata/Metadata.scala b/src/main/scala/org/aphreet/c3/lib/metadata/Metadata.scala index 920b6e9..a77f7de 100755 --- a/src/main/scala/org/aphreet/c3/lib/metadata/Metadata.scala +++ b/src/main/scala/org/aphreet/c3/lib/metadata/Metadata.scala @@ -8,6 +8,7 @@ object Metadata { val DESCRIPTION_META = "x-c3-description" val CONTENT_TYPE = "content-type" val ACL_META = "x-c3-acl" + val ORIGINAL_NAME_META = "x-c3-original-name" val TAGS_META: String = "x-c3-tags" val MSG_CREATOR_META: String = "x-c3-msg-creator" val MSG_DATE_META: String = "x-c3-msg-date" diff --git a/src/main/scala/org/aphreet/c3/snippet/groups/snippet/GroupPageFiles.scala b/src/main/scala/org/aphreet/c3/snippet/groups/snippet/GroupPageFiles.scala index 01b6974..9b072f3 100755 --- a/src/main/scala/org/aphreet/c3/snippet/groups/snippet/GroupPageFiles.scala +++ b/src/main/scala/org/aphreet/c3/snippet/groups/snippet/GroupPageFiles.scala @@ -214,9 +214,9 @@ class GroupPageFiles(data: GroupPageFilesData) extends C3ResourceHelpers JsCmds.SetHtml("right-box-head", { file.name } ) & - JsCmds.SetHtml("description", - { ConvertHelper.ShortString(file.metadata.get(DESCRIPTION_META).getOrElse("")) } - ) & + JsCmds.SetHtml("description_box", + { ConvertHelper.ShortString(file.metadata.get(DESCRIPTION_META).getOrElse("")) } + ) & JsCmds.SetHtml("edit_tags_form", { file.metadata.get(TAGS_META).map(_.split(",").mkString(", ")).getOrElse("") } ) & @@ -742,9 +742,11 @@ class GroupPageFiles(data: GroupPageFilesData) extends C3ResourceHelpers GROUP_ID_META -> data.group.getId, TAGS_META -> tags.trim, DESCRIPTION_META -> description.trim, - ACL_META -> currentDirectory.metadata.get(ACL_META).getOrElse("")) - currentDirectory.createDirectory(name.trim, metadata) - journalServer.foreach(_ ! JournalServerEvent(User.currentUserUnsafe, group, EventType.CreateResources, currentDirectory.fullname + name.trim)) + ACL_META -> currentDirectory.metadata.get(ACL_META).getOrElse(""), + ORIGINAL_NAME_META -> name) + val normalizedName = normilizeFileName(name) + currentDirectory.createDirectory(normalizedName.trim, metadata) + journalServer.foreach(_ ! JournalServerEvent(User.currentUserUnsafe, group, EventType.CreateResources, currentDirectory.fullname + normalizedName.trim)) S.redirectTo(currentPath) // redirect on the same page } } @@ -755,6 +757,10 @@ class GroupPageFiles(data: GroupPageFilesData) extends C3ResourceHelpers "type=submit" #> SHtml.onSubmitUnit(createDirectory) } + private def normilizeFileName(name: String): String = { + name.replace(':', '-').replace('/', '-').replace('\\', '-') + } + private def combineUserMetadata(metadata: scala.collection.Map[String, String]): Map[String, String] = { val combinedMeta: Map[String, Iterable[MetadataElement]] = metadata.map(e => MetadataElement(e._1.replaceFirst("^doc:", ""), e._2, e._1.startsWith("doc:"))).groupBy(_.key) diff --git a/src/main/scala/org/aphreet/c3/snippet/users/snippet/UserListPage.scala b/src/main/scala/org/aphreet/c3/snippet/users/snippet/UserListPage.scala index e455d5a..e01e306 100755 --- a/src/main/scala/org/aphreet/c3/snippet/users/snippet/UserListPage.scala +++ b/src/main/scala/org/aphreet/c3/snippet/users/snippet/UserListPage.scala @@ -61,9 +61,9 @@ class UserListPage extends UserHelper with AdminPageHelper { ".is_admin" #> NodeSeq.Empty & ".enabled *" #> (if (user.enabled.is) "Yes" else "No") & (if (user.id != current.id) { - ".deluser *" #> SHtml.memoize(f => f ++ SHtml.hidden(deleteUser _)) & - ".admin_checkbox " #> SHtml.ajaxCheckbox(user.superUser.is, setSuperAdmin(_)) & - ".resetpwd *" #> SHtml.memoize(f => f ++ SHtml.hidden(resetPassword _)) + ".resetpwd [onclick]" #> SHtml.ajaxInvoke(() => resetPassword) & + ".deluser [onclick]" #> SHtml.ajaxInvoke(() => deleteUser) & + ".admin_checkbox " #> SHtml.ajaxCheckbox(user.superUser.is, setSuperAdmin(_)) } else { ".admin_checkbox" #> NodeSeq.Empty & ".deluser *" #> NodeSeq.Empty & diff --git a/src/main/webapp/groups/files.html b/src/main/webapp/groups/files.html index 5545541..a1944de 100644 --- a/src/main/webapp/groups/files.html +++ b/src/main/webapp/groups/files.html @@ -208,7 +208,7 @@