Skip to content

Commit

Permalink
Files plugin: upload to arbitrary directory (#289)
Browse files Browse the repository at this point in the history
The existing file repository (that is managed by the Proxy plugin) is used as the repository for uploaded files as well.
There are not two different repositories anymore.

So, the recently introduced dm4.files.path POM property is dropped.
Any location inside the Proxy plugin's file repository can be the upload target.
(The file repository's root directory is still configured by the dm4.proxy.files.path POM property.)

The "Upload File" menu item is dropped from the Create menu.
Instead every Folder topic (as revealed through the File Browser) provides an "Uplaod File" button. This button invokes the Upload Dialog. The uploaded file is stored in the directory represented by the respective Folder topic.

Hints for plugin developers:
The method for invoking the Upload Dialog is renamed and takes another mandatory parameter:
{{{
    dm4c.upload_dialog.open(storage_path, callback)
}}}
The new "storage_path" parameter is the storage location for the uploaded file.
The storage path is relative to dm4.proxy.files.path and must begin with a slash.

At REST API level the storage path is appended to the /files request path
{{{
    POST /files//store/file/here
}}}
Note the double slash. The first slash separates the storage_path parameter, the second slash is part of the storage_path parameter (which always begins with slash as aforesaid).

Example: assume this POM setting:
{{{
    <dm4.proxy.files.path>/home/terry/deepamehta-files</dm4.proxy.files.path>
}}}
When posting a multipart/form-data request with a single file part (let's say image.png) to the URI above the file will be stored at
{{{
    /home/terry/deepamehta-files/store/file/here/image.png
}}}

See ticket 289.
  • Loading branch information
jri committed Jul 30, 2012
1 parent 41ae887 commit 3e6c020
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 63 deletions.
Expand Up @@ -5,19 +5,10 @@ dm4c.add_plugin("de.deepamehta.filemanager", function() {
dm4c.add_listener("post_refresh_create_menu", function(type_menu) {
type_menu.add_separator()
type_menu.add_item({label: "New File Browser", handler: do_new_file_browser})
type_menu.add_item({label: "Upload File", handler: do_open_upload_dialog})

function do_new_file_browser() {
dm4c.get_plugin("de.deepamehta.files").create_folder_topic({path: "/"}, true, true)
}

function do_open_upload_dialog() {
dm4c.upload_dialog.show(show_result)

function show_result(result) {
alert("Upload result=" + JSON.stringify(result))
}
}
})

// === Files Listeners ===
Expand Down
@@ -1,6 +1,10 @@
package de.deepamehta.plugins.files;

import de.deepamehta.core.DeepaMehtaTransaction;
import de.deepamehta.core.Topic;
import de.deepamehta.core.model.CompositeValue;
import de.deepamehta.core.model.TopicModel;
import de.deepamehta.core.service.DeepaMehtaService;

import java.io.File;
import java.util.logging.Logger;
Expand All @@ -11,24 +15,25 @@ class FileRepository {

// ------------------------------------------------------------------------------------------------------- Constants

private static final String FILES_PATH = System.getProperty("dm4.files.path");
private static final String FILE_REPOSITORY_PATH = System.getProperty("dm4.proxy.files.path");

// ---------------------------------------------------------------------------------------------- Instance Variables

private File filesDir;
private FileRepositoryContext context;
private File fileRepositoryPath;
private DeepaMehtaService dms;

private Logger logger = Logger.getLogger(getClass().getName());

// ---------------------------------------------------------------------------------------------------- Constructors

FileRepository(FileRepositoryContext context) {
this.filesDir = new File(FILES_PATH);
this.context = context;
FileRepository(DeepaMehtaService dms) {
this.fileRepositoryPath = new File(FILE_REPOSITORY_PATH);
this.dms = dms;
}

// ----------------------------------------------------------------------------------------- Package Private Methods

/* ### FIXME: not used
void initialize() {
if (filesDir.exists()) {
logger.info("Creating file repository ABORTED -- already exists (\"" + filesDir + "\")");
Expand All @@ -37,12 +42,12 @@ void initialize() {
//
logger.info("Creating file repository at \"" + filesDir + "\"");
filesDir.mkdir();
}
} */

StoredFile storeFile(UploadedFile file) {
StoredFile storeFile(UploadedFile file, String storagePath) {
File repoFile = null;
try {
Topic fileTopic = createFileTopic(file);
Topic fileTopic = createFileTopic(file, storagePath);
repoFile = repoFile(fileTopic);
logger.info("Storing file \"" + repoFile + "\" in file repository");
file.write(repoFile);
Expand All @@ -54,24 +59,54 @@ StoredFile storeFile(UploadedFile file) {

// ------------------------------------------------------------------------------------------------- Private Methods

private Topic createFileTopic(UploadedFile file) {
String fileName = file.getName();
String path = "/" + fileName;
String mediaType = file.getMediaType();
long size = file.getSize();
//
return context.createFileTopic(fileName, path, mediaType, size);
/**
* @param storagePath Begins with "/". Has no "/" at end.
*/
private Topic createFileTopic(UploadedFile file, String storagePath) {
DeepaMehtaTransaction tx = dms.beginTx();
try {
Topic fileTopic = dms.createTopic(new TopicModel("dm4.files.file"), null); // FIXME: clientState=null
//
String fileName = fileTopic.getId() + "-" + file.getName();
String path = storagePath + "/" + fileName;
String mediaType = file.getMediaType();
long size = file.getSize();
//
CompositeValue comp = new CompositeValue();
comp.put("dm4.files.file_name", fileName);
comp.put("dm4.files.path", path);
if (mediaType != null) {
comp.put("dm4.files.media_type", mediaType);
}
comp.put("dm4.files.size", size);
//
fileTopic.setCompositeValue(comp, null, null);
//
tx.success();
return fileTopic;
} catch (Exception e) {
logger.warning("ROLLBACK!");
throw new RuntimeException("Creating file topic for uploaded file failed (" +
file + ", storagePath=\"" + storagePath + "\")", e);
} finally {
tx.finish();
}
}

/**
* Calculates the storage location for the file represented by the specified topic.
* Calculates the storage location for the file represented by the specified file topic.
*/
private File repoFile(Topic fileTopic) {
String repoFileName = fileTopic.getId() + "-" + fileName(fileTopic);
return new File(filesDir, repoFileName);
return new File(fileRepositoryPath, path(fileTopic));
}

// ---

private String fileName(Topic fileTopic) {
return fileTopic.getCompositeValue().getString("dm4.files.file_name");
}

private String path(Topic fileTopic) {
return fileTopic.getCompositeValue().getString("dm4.files.path");
}
}

This file was deleted.

Expand Up @@ -33,13 +33,13 @@

@Path("/files")
@Produces("application/json")
public class FilesPlugin extends PluginActivator implements FilesService, InitializePluginListener,
FileRepositoryContext, PluginServiceArrivedListener,
PluginServiceGoneListener {
public class FilesPlugin extends PluginActivator implements FilesService, InitializePluginListener,
PluginServiceArrivedListener,
PluginServiceGoneListener {

// ---------------------------------------------------------------------------------------------- Instance Variables

private FileRepository fileRepository = new FileRepository(this);
private FileRepository fileRepository;
private ProxyService proxyService;

private Logger logger = Logger.getLogger(getClass().getName());
Expand Down Expand Up @@ -135,11 +135,13 @@ public Topic createChildFolderTopic(@PathParam("id") long folderTopicId, @PathPa
// ---

@POST
@Path("/{storage_path:.+}")
@Consumes("multipart/form-data")
@Override
public StoredFile storeFile(UploadedFile file) {
public StoredFile storeFile(UploadedFile file, @PathParam("storage_path") String storagePath) {
try {
return fileRepository.storeFile(file);
logger.info(file + ", storagePath=\"" + storagePath + "\"");
return fileRepository.storeFile(file, storagePath);
} catch (Exception e) {
throw new WebApplicationException(new RuntimeException("Uploading " + file + " failed", e));
}
Expand Down Expand Up @@ -173,7 +175,7 @@ public void openFile(@PathParam("id") long fileTopicId) {

@Override
public void initializePlugin() {
fileRepository.initialize();
fileRepository = new FileRepository(dms);
}

// ---
Expand Down Expand Up @@ -218,8 +220,7 @@ private Topic getFolderTopic(String path) {

// ---

@Override
public Topic createFileTopic(String fileName, String path, String mediaType, long size) {
private Topic createFileTopic(String fileName, String path, String mediaType, long size) {
CompositeValue comp = new CompositeValue();
comp.put("dm4.files.file_name", fileName);
comp.put("dm4.files.path", path);
Expand Down
Expand Up @@ -13,7 +13,7 @@
* resource.
* <p>
* Client-side support: the <code>deepamehta-webclient</code> plugin provides an utility method
* <code>dm4c.upload_dialog.show()</code> that allows the user to choose and upload a file.</p>
* <code>dm4c.upload_dialog.open()</code> that allows the user to choose and upload a file.</p>
* <p>
* At server-side a plugin accesses the upload file via the
* {@link de.deepamehta.core.service.Plugin#executeCommandHook}. ### FIXDOC</p>
Expand Down Expand Up @@ -50,6 +50,9 @@ public long getSize() {
return fileItem.getSize();
}

/**
* Returns the content type passed by the browser or null if not defined.
*/
public String getMediaType() {
return fileItem.getContentType();
}
Expand Down
Expand Up @@ -33,7 +33,7 @@ public interface FilesService extends PluginService {

// ---

StoredFile storeFile(UploadedFile file);
StoredFile storeFile(UploadedFile file, String storagePath);

// ---

Expand Down
14 changes: 14 additions & 0 deletions modules/dm4-files/src/main/resources/web/script/plugin.js
Expand Up @@ -118,6 +118,20 @@ dm4c.add_plugin("de.deepamehta.files", function() {
}
})

dm4c.add_listener("topic_commands", function(topic) {
if (topic.type_uri == "dm4.files.folder") {
return [{label: "Upload File", handler: do_open_upload_dialog, context: "detail-panel-show"}]
}

function do_open_upload_dialog() {
var storage_path = topic.get("dm4.files.path")
dm4c.upload_dialog.open(storage_path, show_response)

function show_response(response) {
alert("Upload response=" + JSON.stringify(response))
}
}
})


// ------------------------------------------------------------------------------------------------------ Public API
Expand Down
Expand Up @@ -3,7 +3,6 @@ function UploadDialog() {
var UPLOAD_DIALOG_WIDTH = "50em"

var upload_form = $("<form>", {
action: "/files",
method: "post",
enctype: "multipart/form-data",
target: "upload-target"
Expand All @@ -19,26 +18,26 @@ function UploadDialog() {
// -------------------------------------------------------------------------------------------------- Public Methods

/**
* @param command the command (a string) send to the server along with the selected file. ### FIXME
* @param callback the function that is invoked once the file has been uploaded and processed at server-side.
* One argument is passed to that function: the object (deserialzed JSON) returned by the
* (server-side) executeCommandHook. ### FIXDOC
* @param storage_path the file repository path (a string) to upload the selected file to. Must begin with "/".
* @param callback the function that is invoked once the file has been uploaded and processed at
* server-side. One argument is passed to that function: the object (deserialzed JSON)
* returned by the (server-side) executeCommandHook. ### FIXDOC
*/
this.show = function(callback) {
// $("#upload-dialog form").attr("action", "/files/" + command) // ### FIXME
this.open = function(storage_path, callback) {
upload_form.attr("action", "/files/" + storage_path)
$("#upload-dialog").dialog("open")
// bind handler. Note: the previous handler must be removed
upload_target.unbind("load")
// bind handler
upload_target.unbind("load") // Note: the previous handler must be removed
upload_target.load(upload_complete)

function upload_complete() {
$("#upload-dialog").dialog("close")
// Note: iframes must be accessed via window.frames
var result = $("pre", window.frames["upload-target"].document).text()
var response = $("pre", window.frames["upload-target"].document).text()
try {
callback(JSON.parse(result))
callback(JSON.parse(response))
} catch (e) {
alert("Invalid server response: \"" + result + "\"\n\nException=" + JSON.stringify(e))
alert("Upload failed: \"" + response + "\"\n\nException=" + JSON.stringify(e))
}
}
}
Expand Down
Expand Up @@ -687,7 +687,7 @@ function Webclient() {
dataType: "script",
async: async || false,
error: function(jq_xhr, text_status, error_thrown) {
throw "WebclientError: loading script failed (" + text_status + ": " + error_thrown + ")"
throw "WebclientError: loading script " + url + " failed (" + text_status + ": " + error_thrown + ")"
}
})
}
Expand Down
1 change: 0 additions & 1 deletion pom.xml
Expand Up @@ -21,7 +21,6 @@
<!-- The other dm4.* properties are required by 'run' only. They are placed here for clarity. -->
<properties>
<dm4.database.path>${project.basedir}/deepamehta-db</dm4.database.path>
<dm4.files.path>${project.basedir}/deepamehta-files</dm4.files.path>
<dm4.proxy.files.path></dm4.proxy.files.path>
<dm4.proxy.net.filter>127.0.0.1/32</dm4.proxy.net.filter>
</properties>
Expand Down

0 comments on commit 3e6c020

Please sign in to comment.