mirror of
https://github.com/sismics/docs.git
synced 2024-11-26 07:34:55 +01:00
commit
8477920475
@ -28,6 +28,7 @@ Features
|
||||
- Tag system with relations
|
||||
- Multi-users ACL system
|
||||
- Audit log
|
||||
- Comments
|
||||
- Document sharing by URL
|
||||
- RESTful Web API
|
||||
- Fully featured Android client
|
||||
|
@ -71,12 +71,11 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/debug" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.2.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.1/jars" />
|
||||
@ -84,17 +83,23 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/ndk" />
|
||||
@ -112,12 +117,12 @@
|
||||
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="eventbus-2.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="recyclerview-v7-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
|
||||
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
|
||||
<orderEntry type="library" exported="" name="eventbus-2.4.1" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,20 +1,20 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.3.0'
|
||||
classpath 'com.android.tools.build:gradle:2.0.0-alpha1'
|
||||
}
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "23.0.2"
|
||||
buildToolsVersion '23.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
@ -54,6 +54,6 @@ dependencies {
|
||||
compile 'com.android.support:recyclerview-v7:22.+'
|
||||
compile 'com.loopj.android:android-async-http:1.4.6'
|
||||
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||
compile 'de.greenrobot:eventbus:2.4.0'
|
||||
compile 'de.greenrobot:eventbus:2.4.1'
|
||||
compile 'com.shamanland:fab:0.0.6'
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ public class MainApplication extends Application {
|
||||
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
|
||||
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||
|
||||
// TODO google docs app: right drawer with all actions, with acls, with deep metadatas
|
||||
// TODO Provide documents to intent action get content
|
||||
|
||||
super.onCreate();
|
||||
@ -28,6 +27,7 @@ public class MainApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
BitmapAjaxCallback.clearCache();
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,15 @@ import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
@ -31,7 +35,10 @@ import android.widget.Toast;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.adapter.AclListAdapter;
|
||||
import com.sismics.docs.adapter.CommentListAdapter;
|
||||
import com.sismics.docs.adapter.FilePagerAdapter;
|
||||
import com.sismics.docs.event.CommentAddEvent;
|
||||
import com.sismics.docs.event.CommentDeleteEvent;
|
||||
import com.sismics.docs.event.DocumentDeleteEvent;
|
||||
import com.sismics.docs.event.DocumentEditEvent;
|
||||
import com.sismics.docs.event.DocumentFullscreenEvent;
|
||||
@ -40,6 +47,7 @@ import com.sismics.docs.event.FileDeleteEvent;
|
||||
import com.sismics.docs.fragment.DocShareFragment;
|
||||
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.resource.CommentResource;
|
||||
import com.sismics.docs.resource.DocumentResource;
|
||||
import com.sismics.docs.resource.FileResource;
|
||||
import com.sismics.docs.service.FileUploadService;
|
||||
@ -83,6 +91,11 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
*/
|
||||
private FilePagerAdapter filePagerAdapter;
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*/
|
||||
private CommentListAdapter commentListAdapter;
|
||||
|
||||
/**
|
||||
* Document displayed.
|
||||
*/
|
||||
@ -241,6 +254,39 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// Button add a comment
|
||||
ImageButton imageButton = (ImageButton) findViewById(R.id.addCommentBtn);
|
||||
imageButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final EditText commentEditText = (EditText) findViewById(R.id.commentEditText);
|
||||
if (commentEditText.getText().length() == 0) {
|
||||
// No content for the new comment
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.adding_comment, Toast.LENGTH_LONG).show();
|
||||
|
||||
CommentResource.add(DocumentViewActivity.this,
|
||||
DocumentViewActivity.this.document.optString("id"),
|
||||
commentEditText.getText().toString(),
|
||||
new JsonHttpResponseHandler() {
|
||||
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||
EventBus.getDefault().post(new CommentAddEvent(response));
|
||||
commentEditText.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.comment_add_failure, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Grab the comments
|
||||
updateComments();
|
||||
|
||||
// Grab the attached files
|
||||
updateFiles();
|
||||
|
||||
@ -268,6 +314,15 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.comments:
|
||||
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
} else {
|
||||
drawerLayout.openDrawer(GravityCompat.START);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.download_file:
|
||||
downloadCurrentFile();
|
||||
return true;
|
||||
@ -507,6 +562,36 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment add event has been fired.
|
||||
*
|
||||
* @param event Comment add event
|
||||
*/
|
||||
public void onEventMainThread(CommentAddEvent event) {
|
||||
if (commentListAdapter == null) return;
|
||||
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
commentListAdapter.add(event.getComment());
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment delete event has been fired.
|
||||
*
|
||||
* @param event Comment add event
|
||||
*/
|
||||
public void onEventMainThread(CommentDeleteEvent event) {
|
||||
if (commentListAdapter == null) return;
|
||||
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
commentListAdapter.remove(event.getCommentId());
|
||||
if (commentListAdapter.getCount() == 0) {
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
listView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (document == null) return;
|
||||
@ -572,6 +657,89 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
switch (view.getId()) {
|
||||
case R.id.commentListView:
|
||||
if (commentListAdapter == null || document == null) return;
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
JSONObject comment = commentListAdapter.getItem(info.position);
|
||||
boolean writable = document.optBoolean("writable");
|
||||
String creator = comment.optString("creator");
|
||||
String username = ApplicationContext.getInstance().getUserInfo().optString("username");
|
||||
if (writable || creator.equals(username)) {
|
||||
menu.add(Menu.NONE, 0, 0, getString(R.string.comment_delete));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
// Use real ids if more than one item someday
|
||||
if (item.getItemId() == 0) {
|
||||
// Delete a comment
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
|
||||
if (commentListAdapter == null) return false;
|
||||
JSONObject comment = commentListAdapter.getItem(info.position);
|
||||
final String commentId = comment.optString("id");
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.deleting_comment, Toast.LENGTH_LONG).show();
|
||||
|
||||
CommentResource.remove(DocumentViewActivity.this, commentId, new JsonHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||
EventBus.getDefault().post(new CommentDeleteEvent(commentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.error_deleting_comment, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh comments list.
|
||||
*/
|
||||
private void updateComments() {
|
||||
if (document == null) return;
|
||||
|
||||
final View progressBar = findViewById(R.id.commentProgressView);
|
||||
final TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
final ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.GONE);
|
||||
registerForContextMenu(listView);
|
||||
|
||||
CommentResource.list(this, document.optString("id"), new JsonHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||
JSONArray comments = response.optJSONArray("comments");
|
||||
commentListAdapter = new CommentListAdapter(comments);
|
||||
listView.setAdapter(commentListAdapter);
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (comments.length() == 0) {
|
||||
listView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||
emptyView.setText(R.string.error_loading_comments);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh files list.
|
||||
*/
|
||||
|
@ -0,0 +1,127 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.androidquery.AQuery;
|
||||
import com.androidquery.callback.BitmapAjaxCallback;
|
||||
import com.sismics.docs.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentListAdapter extends BaseAdapter {
|
||||
/**
|
||||
* AQuery.
|
||||
*/
|
||||
private AQuery aq;
|
||||
|
||||
/**
|
||||
* Tags.
|
||||
*/
|
||||
private List<JSONObject> commentList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*
|
||||
* @param commentsArray Comments
|
||||
*/
|
||||
public CommentListAdapter(JSONArray commentsArray) {
|
||||
for (int i = 0; i < commentsArray.length(); i++) {
|
||||
commentList.add(commentsArray.optJSONObject(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return commentList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getItem(int position) {
|
||||
return commentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).optString("id").hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
if (view == null) {
|
||||
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = vi.inflate(R.layout.comment_list_item, parent, false);
|
||||
}
|
||||
|
||||
if (aq == null) {
|
||||
aq = new AQuery(view);
|
||||
} else {
|
||||
aq.recycle(view);
|
||||
}
|
||||
|
||||
// Fill the view
|
||||
JSONObject comment = getItem(position);
|
||||
TextView creatorTextView = (TextView) view.findViewById(R.id.creatorTextView);
|
||||
TextView dateTextView = (TextView) view.findViewById(R.id.dateTextView);
|
||||
TextView contentTextView = (TextView) view.findViewById(R.id.contentTextView);
|
||||
ImageView gravatarImageView = (ImageView) view.findViewById(R.id.gravatarImageView);
|
||||
creatorTextView.setText(comment.optString("creator"));
|
||||
dateTextView.setText(DateFormat.getDateFormat(dateTextView.getContext()).format(new Date(comment.optLong("create_date"))));
|
||||
contentTextView.setText(comment.optString("content"));
|
||||
|
||||
// Gravatar image
|
||||
String gravatarUrl = "http://www.gravatar.com/avatar/" + comment.optString("creator_gravatar") + "?s=128d=identicon";
|
||||
if (aq.shouldDelay(position, view, parent, gravatarUrl)) {
|
||||
aq.id(gravatarImageView).image((Bitmap) null);
|
||||
} else {
|
||||
aq.id(gravatarImageView).image(new BitmapAjaxCallback()
|
||||
.url(gravatarUrl)
|
||||
.animation(AQuery.FADE_IN_NETWORK)
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new comment.
|
||||
*
|
||||
* @param comment Comment
|
||||
*/
|
||||
public void add(JSONObject comment) {
|
||||
commentList.add(comment);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a comment.
|
||||
*
|
||||
* @param commentId Comment ID
|
||||
*/
|
||||
public void remove(String commentId) {
|
||||
for (JSONObject comment : commentList) {
|
||||
if (comment.optString("id").equals(commentId)) {
|
||||
commentList.remove(comment);
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.sismics.docs.event;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Comment add event.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentAddEvent {
|
||||
/**
|
||||
* Comment.
|
||||
*/
|
||||
private JSONObject comment;
|
||||
|
||||
/**
|
||||
* Create a comment add event.
|
||||
*
|
||||
* @param comment Comment
|
||||
*/
|
||||
public CommentAddEvent(JSONObject comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of comment.
|
||||
*
|
||||
* @return comment
|
||||
*/
|
||||
public JSONObject getComment() {
|
||||
return comment;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.sismics.docs.event;
|
||||
|
||||
/**
|
||||
* Comment delete event.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentDeleteEvent {
|
||||
/**
|
||||
* Comment ID.
|
||||
*/
|
||||
private String commentId;
|
||||
|
||||
/**
|
||||
* Create a comment add event.
|
||||
*
|
||||
* @param commentId Comment ID
|
||||
*/
|
||||
public CommentDeleteEvent(String commentId) {
|
||||
this.commentId = commentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of commentId.
|
||||
*
|
||||
* @return commentId
|
||||
*/
|
||||
public String getCommentId() {
|
||||
return commentId;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.loopj.android.http.RequestParams;
|
||||
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||
|
||||
|
||||
/**
|
||||
* Access to /comment API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class CommentResource extends BaseResource {
|
||||
/**
|
||||
* GET /comment/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param responseHandler Callback
|
||||
*/
|
||||
public static void list(Context context, String documentId, JsonHttpResponseHandler responseHandler) {
|
||||
init(context);
|
||||
|
||||
client.get(getApiUrl(context) + "/comment/" + documentId, responseHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /comment.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param content Comment content
|
||||
* @param responseHandler Callback
|
||||
*/
|
||||
public static void add(Context context, String documentId, String content, JsonHttpResponseHandler responseHandler) {
|
||||
init(context);
|
||||
|
||||
RequestParams params = new RequestParams();
|
||||
params.put("id", documentId);
|
||||
params.put("content", content);
|
||||
client.put(getApiUrl(context) + "/comment", params, responseHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /comment/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param commentId Comment ID
|
||||
* @param responseHandler Callback
|
||||
*/
|
||||
public static void remove(Context context, String commentId, JsonHttpResponseHandler responseHandler) {
|
||||
init(context);
|
||||
|
||||
client.delete(getApiUrl(context) + "/comment/" + commentId, responseHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending requests.
|
||||
*
|
||||
* @param context Context
|
||||
*/
|
||||
public static void cancel(Context context) {
|
||||
client.cancelRequests(context, true);
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 296 B |
Binary file not shown.
After Width: | Height: | Size: 448 B |
Binary file not shown.
After Width: | Height: | Size: 359 B |
Binary file not shown.
After Width: | Height: | Size: 565 B |
57
docs-android/app/src/main/res/layout/comment_list_item.xml
Normal file
57
docs-android/app/src/main/res/layout/comment_list_item.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gravatarImageView"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/creatorTextView"
|
||||
android:layout_toRightOf="@id/gravatarImageView"
|
||||
android:layout_toEndOf="@id/gravatarImageView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#212121"
|
||||
android:text="Creator"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contentTextView"
|
||||
android:layout_toRightOf="@id/gravatarImageView"
|
||||
android:layout_toEndOf="@id/gravatarImageView"
|
||||
android:layout_below="@id/creatorTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="#212121"
|
||||
android:text="Comment content"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateTextView"
|
||||
android:layout_toRightOf="@id/gravatarImageView"
|
||||
android:layout_toEndOf="@id/gravatarImageView"
|
||||
android:layout_below="@id/contentTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="#888"
|
||||
android:text="2015-11-10"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
</RelativeLayout>
|
@ -37,6 +37,109 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Left drawer -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/left_drawer"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:orientation="vertical"
|
||||
android:clickable="true"
|
||||
android:background="#fff"
|
||||
android:elevation="5dp">
|
||||
|
||||
<!-- Comments -->
|
||||
|
||||
<TextView
|
||||
android:drawableStart="@drawable/ic_comment_black_24dp"
|
||||
android:drawableLeft="@drawable/ic_comment_black_24dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/primary_text_default_material_light"
|
||||
android:text="@string/comments"
|
||||
android:layout_margin="12dp"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#eee"/>
|
||||
|
||||
<ListView
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/commentListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/transparent"
|
||||
android:transcriptMode="normal"
|
||||
android:dividerHeight="0dp"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/commentProgressView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:progressBarStyle"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/commentEmptyView"
|
||||
android:visibility="gone"
|
||||
android:padding="12dp"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="@string/no_comments"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#eee"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="6dp"
|
||||
android:gravity="center">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/commentEditText"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:inputType="text"
|
||||
android:hint="@string/add_comment"
|
||||
android:maxLength="4000"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/addCommentBtn"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_send_grey600_24dp"
|
||||
android:contentDescription="@string/send"
|
||||
android:background="?android:selectableItemBackground"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Right drawer -->
|
||||
|
||||
<LinearLayout
|
||||
|
@ -9,6 +9,12 @@
|
||||
android:title="@string/toggle_informations">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/comments"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/comments">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/download_file"
|
||||
app:showAsAction="collapseActionView"
|
||||
|
@ -107,6 +107,16 @@
|
||||
<string name="all_languages">All languages</string>
|
||||
<string name="toggle_informations">Toggle informations</string>
|
||||
<string name="who_can_access">Who can access</string>
|
||||
<string name="comments">Comments</string>
|
||||
<string name="no_comments">No comments</string>
|
||||
<string name="error_loading_comments">Error loading comments</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="add_comment">Add a comment</string>
|
||||
<string name="comment_add_failure">Error adding a comment</string>
|
||||
<string name="adding_comment">Adding a comment</string>
|
||||
<string name="comment_delete">Delete comment</string>
|
||||
<string name="deleting_comment">Deleting comment</string>
|
||||
<string name="error_deleting_comment">Error deleting comment</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Wed Nov 26 21:58:48 CET 2014
|
||||
#Mon Nov 23 20:12:30 CET 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
|
||||
|
@ -0,0 +1 @@
|
||||
update T_CONFIG set CFG_VALUE_C = 'RAM' where CFG_ID_C = 'LUCENE_DIRECTORY_STORAGE';
|
@ -101,6 +101,8 @@ public class UserDao {
|
||||
|
||||
// Update the user
|
||||
userFromDb.setEmail(user.getEmail());
|
||||
userFromDb.setStorageQuota(user.getStorageQuota());
|
||||
userFromDb.setStorageCurrent(user.getStorageCurrent());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE);
|
||||
@ -226,7 +228,7 @@ public class UserDao {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3");
|
||||
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5");
|
||||
sb.append(" from T_USER u ");
|
||||
|
||||
// Add search criterias
|
||||
@ -255,6 +257,8 @@ public class UserDao {
|
||||
userDto.setUsername((String) o[i++]);
|
||||
userDto.setEmail((String) o[i++]);
|
||||
userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
userDto.setStorageCurrent(((Number) o[i++]).longValue());
|
||||
userDto.setStorageQuota(((Number) o[i++]).longValue());
|
||||
userDtoList.add(userDto);
|
||||
}
|
||||
paginatedList.setResultList(userDtoList);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.sismics.docs.core.dao.jpa.dto;
|
||||
|
||||
|
||||
/**
|
||||
* User DTO.
|
||||
*
|
||||
@ -26,6 +27,16 @@ public class UserDto {
|
||||
*/
|
||||
private Long createTimestamp;
|
||||
|
||||
/**
|
||||
* Storage quota.
|
||||
*/
|
||||
private Long storageQuota;
|
||||
|
||||
/**
|
||||
* Storage current usage.
|
||||
*/
|
||||
private Long storageCurrent;
|
||||
|
||||
/**
|
||||
* Getter of id.
|
||||
*
|
||||
@ -89,6 +100,22 @@ public class UserDto {
|
||||
return createTimestamp;
|
||||
}
|
||||
|
||||
public Long getStorageQuota() {
|
||||
return storageQuota;
|
||||
}
|
||||
|
||||
public void setStorageQuota(Long storageQuota) {
|
||||
this.storageQuota = storageQuota;
|
||||
}
|
||||
|
||||
public Long getStorageCurrent() {
|
||||
return storageCurrent;
|
||||
}
|
||||
|
||||
public void setStorageCurrent(Long storageCurrent) {
|
||||
this.storageCurrent = storageCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of createTimestamp.
|
||||
*
|
||||
|
@ -54,6 +54,18 @@ public class User implements Loggable {
|
||||
@Column(name = "USE_EMAIL_C", nullable = false, length = 100)
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* Storage quota.
|
||||
*/
|
||||
@Column(name = "USE_STORAGEQUOTA_N", nullable = false)
|
||||
private Long storageQuota;
|
||||
|
||||
/**
|
||||
* Storage current usage.
|
||||
*/
|
||||
@Column(name = "USE_STORAGECURRENT_N", nullable = false)
|
||||
private Long storageCurrent;
|
||||
|
||||
/**
|
||||
* Creation date.
|
||||
*/
|
||||
@ -66,149 +78,87 @@ public class User implements Loggable {
|
||||
@Column(name = "USE_DELETEDATE_D")
|
||||
private Date deleteDate;
|
||||
|
||||
/**
|
||||
* Getter of id.
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of id.
|
||||
*
|
||||
* @param id id
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of roleId.
|
||||
*
|
||||
* @return roleId
|
||||
*/
|
||||
public String getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of roleId.
|
||||
*
|
||||
* @param roleId roleId
|
||||
*/
|
||||
public void setRoleId(String roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of username.
|
||||
*
|
||||
* @return username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of username.
|
||||
*
|
||||
* @param username username
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of password.
|
||||
*
|
||||
* @return password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of password.
|
||||
*
|
||||
* @param password password
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of email.
|
||||
*
|
||||
* @return email
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of email.
|
||||
*
|
||||
* @param email email
|
||||
*/
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of createDate.
|
||||
*
|
||||
* @return createDate
|
||||
*/
|
||||
public Date getCreateDate() {
|
||||
return createDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of createDate.
|
||||
*
|
||||
* @param createDate createDate
|
||||
*/
|
||||
public void setCreateDate(Date createDate) {
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of deleteDate.
|
||||
*
|
||||
* @return deleteDate
|
||||
*/
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of deleteDate.
|
||||
*
|
||||
* @param deleteDate deleteDate
|
||||
*/
|
||||
public void setDeleteDate(Date deleteDate) {
|
||||
this.deleteDate = deleteDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter de privateKey.
|
||||
* @return privateKey
|
||||
*/
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter de privateKey.
|
||||
* @param privateKey privateKey
|
||||
*/
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public Long getStorageQuota() {
|
||||
return storageQuota;
|
||||
}
|
||||
|
||||
public void setStorageQuota(Long storageQuota) {
|
||||
this.storageQuota = storageQuota;
|
||||
}
|
||||
|
||||
public Long getStorageCurrent() {
|
||||
return storageCurrent;
|
||||
}
|
||||
|
||||
public void setStorageCurrent(Long storageCurrent) {
|
||||
this.storageCurrent = storageCurrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.sismics.docs.core.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
@ -56,10 +56,10 @@ public class IndexingService extends AbstractScheduledService {
|
||||
directory = new RAMDirectory();
|
||||
log.info("Using RAM Lucene storage");
|
||||
} else if (luceneStorageConfig.equals(Constants.LUCENE_DIRECTORY_STORAGE_FILE)) {
|
||||
File luceneDirectory = DirectoryUtil.getLuceneDirectory();
|
||||
Path luceneDirectory = DirectoryUtil.getLuceneDirectory();
|
||||
log.info("Using file Lucene storage: {}", luceneDirectory);
|
||||
try {
|
||||
directory = new SimpleFSDirectory(luceneDirectory, new SimpleFSLockFactory());
|
||||
directory = new SimpleFSDirectory(luceneDirectory.toFile(), new SimpleFSLockFactory());
|
||||
} catch (IOException e) {
|
||||
log.error("Error initializing Lucene index", e);
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
@ -17,27 +20,31 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Base data directory
|
||||
*/
|
||||
public static File getBaseDataDirectory() {
|
||||
File baseDataDir = null;
|
||||
public static Path getBaseDataDirectory() {
|
||||
Path baseDataDir = null;
|
||||
if (StringUtils.isNotBlank(EnvironmentUtil.getDocsHome())) {
|
||||
// If the docs.home property is set then use it
|
||||
baseDataDir = new File(EnvironmentUtil.getDocsHome());
|
||||
baseDataDir = Paths.get(EnvironmentUtil.getDocsHome());
|
||||
} else if (EnvironmentUtil.isUnitTest()) {
|
||||
// For unit testing, use a temporary directory
|
||||
baseDataDir = new File(System.getProperty("java.io.tmpdir"));
|
||||
baseDataDir = Paths.get(System.getProperty("java.io.tmpdir"));
|
||||
} else {
|
||||
// We are in a webapp environment and nothing is specified, use the default directory for this OS
|
||||
if (EnvironmentUtil.isUnix()) {
|
||||
baseDataDir = new File("/var/docs");
|
||||
baseDataDir = Paths.get("/var/docs");
|
||||
} if (EnvironmentUtil.isWindows()) {
|
||||
baseDataDir = new File(EnvironmentUtil.getWindowsAppData() + "\\Sismics\\Docs");
|
||||
baseDataDir = Paths.get(EnvironmentUtil.getWindowsAppData() + "\\Sismics\\Docs");
|
||||
} else if (EnvironmentUtil.isMacOs()) {
|
||||
baseDataDir = new File(EnvironmentUtil.getMacOsUserHome() + "/Library/Sismics/Docs");
|
||||
baseDataDir = Paths.get(EnvironmentUtil.getMacOsUserHome() + "/Library/Sismics/Docs");
|
||||
}
|
||||
}
|
||||
|
||||
if (baseDataDir != null && !baseDataDir.isDirectory()) {
|
||||
baseDataDir.mkdirs();
|
||||
if (baseDataDir != null && !Files.isDirectory(baseDataDir)) {
|
||||
try {
|
||||
Files.createDirectories(baseDataDir);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return baseDataDir;
|
||||
@ -48,7 +55,7 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Database directory.
|
||||
*/
|
||||
public static File getDbDirectory() {
|
||||
public static Path getDbDirectory() {
|
||||
return getDataSubDirectory("db");
|
||||
}
|
||||
|
||||
@ -57,7 +64,7 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Lucene indexes directory.
|
||||
*/
|
||||
public static File getLuceneDirectory() {
|
||||
public static Path getLuceneDirectory() {
|
||||
return getDataSubDirectory("lucene");
|
||||
}
|
||||
|
||||
@ -66,7 +73,7 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Storage directory.
|
||||
*/
|
||||
public static File getStorageDirectory() {
|
||||
public static Path getStorageDirectory() {
|
||||
return getDataSubDirectory("storage");
|
||||
}
|
||||
|
||||
@ -75,7 +82,7 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Log directory.
|
||||
*/
|
||||
public static File getLogDirectory() {
|
||||
public static Path getLogDirectory() {
|
||||
return getDataSubDirectory("log");
|
||||
}
|
||||
|
||||
@ -84,11 +91,15 @@ public class DirectoryUtil {
|
||||
*
|
||||
* @return Subdirectory
|
||||
*/
|
||||
private static File getDataSubDirectory(String subdirectory) {
|
||||
File baseDataDir = getBaseDataDirectory();
|
||||
File directory = new File(baseDataDir.getPath() + File.separator + subdirectory);
|
||||
if (!directory.isDirectory()) {
|
||||
directory.mkdirs();
|
||||
private static Path getDataSubDirectory(String subdirectory) {
|
||||
Path baseDataDir = getBaseDataDirectory();
|
||||
Path directory = baseDataDir.resolve(subdirectory);
|
||||
if (!Files.isDirectory(directory)) {
|
||||
try {
|
||||
Files.createDirectories(directory);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
@ -132,7 +130,7 @@ public class FileUtil {
|
||||
*/
|
||||
public static void save(InputStream inputStream, File file, String privateKey) throws Exception {
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher(privateKey);
|
||||
Path path = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId());
|
||||
Path path = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
Files.copy(new CipherInputStream(inputStream, cipher), path);
|
||||
|
||||
// Generate file variations
|
||||
@ -172,21 +170,15 @@ public class FileUtil {
|
||||
image.flush();
|
||||
|
||||
// Write "web" encrypted image
|
||||
java.io.File outputFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_web").toFile();
|
||||
OutputStream outputStream = new CipherOutputStream(new FileOutputStream(outputFile), cipher);
|
||||
try {
|
||||
Path outputFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_web");
|
||||
try (OutputStream outputStream = new CipherOutputStream(Files.newOutputStream(outputFile), cipher)) {
|
||||
ImageUtil.writeJpeg(web, outputStream);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
// Write "thumb" encrypted image
|
||||
outputFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_thumb").toFile();
|
||||
outputStream = new CipherOutputStream(new FileOutputStream(outputFile), cipher);
|
||||
try {
|
||||
outputFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_thumb");
|
||||
try (OutputStream outputStream = new CipherOutputStream(Files.newOutputStream(outputFile), cipher)) {
|
||||
ImageUtil.writeJpeg(thumbnail, outputStream);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,20 +187,21 @@ public class FileUtil {
|
||||
* Remove a file from the storage filesystem.
|
||||
*
|
||||
* @param file File to delete
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void delete(File file) {
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_web").toFile();
|
||||
java.io.File thumbnailFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_thumb").toFile();
|
||||
public static void delete(File file) throws IOException {
|
||||
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
Path webFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_web");
|
||||
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_thumb");
|
||||
|
||||
if (storedFile.exists()) {
|
||||
storedFile.delete();
|
||||
if (Files.exists(storedFile)) {
|
||||
Files.delete(storedFile);
|
||||
}
|
||||
if (webFile.exists()) {
|
||||
webFile.delete();
|
||||
if (Files.exists(webFile)) {
|
||||
Files.delete(webFile);
|
||||
}
|
||||
if (thumbnailFile.exists()) {
|
||||
thumbnailFile.delete();
|
||||
if (Files.exists(thumbnailFile)) {
|
||||
Files.delete(thumbnailFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,97 +0,0 @@
|
||||
package com.sismics.docs.core.util.math;
|
||||
|
||||
|
||||
/**
|
||||
* Classe utilitaire pour les calculs
|
||||
*
|
||||
* @author bgamard
|
||||
*
|
||||
*/
|
||||
public class MathUtil {
|
||||
|
||||
/**
|
||||
* Arrondi à 2 décimales près
|
||||
*
|
||||
* @param d Nombre à arrondir
|
||||
* @return Nombre arrondi
|
||||
*/
|
||||
public static Double round(Double d) {
|
||||
return Math.round(d * 100.0) / 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contraint une valeur entre min et max.
|
||||
*
|
||||
* @param value Valeur
|
||||
* @param min Minimum
|
||||
* @param max Maximum
|
||||
* @return Valeur contrainte
|
||||
*/
|
||||
public static double clip(double value, double min, double max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpole une valeur entre deux points.
|
||||
*
|
||||
* @param x Valeur à interpoler
|
||||
* @param x1 Point 1 (x)
|
||||
* @param y1 Point 1 (y)
|
||||
* @param x2 Point 2 (x)
|
||||
* @param y2 Point 2 (y)
|
||||
* @return Valeur interpolée
|
||||
*/
|
||||
public static double interpolate(double x, double x1, double y1, double x2, double y2) {
|
||||
double alpha = (x - x1) / (x2 - x1);
|
||||
|
||||
return y1 * (1 - alpha) + y2 * alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne un Double depuis un Number.
|
||||
*
|
||||
* @param number Number
|
||||
* @return Double
|
||||
*/
|
||||
public static Double getDoubleFromNumber(Number number) {
|
||||
if (number == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return number.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne un Integer depuis un Number.
|
||||
*
|
||||
* @param number Number
|
||||
* @return Integer
|
||||
*/
|
||||
public static Integer getIntegerFromNumber(Number number) {
|
||||
if (number == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return number.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne un Long depuis un Number.
|
||||
*
|
||||
* @param number Number
|
||||
* @return Long
|
||||
*/
|
||||
public static Long getLongFromNumber(Number number) {
|
||||
if (number == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return number.longValue();
|
||||
}
|
||||
}
|
@ -1,6 +1,16 @@
|
||||
package com.sismics.util.jpa;
|
||||
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
@ -8,15 +18,7 @@ import org.hibernate.service.ServiceRegistryBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
|
||||
/**
|
||||
* Entity manager factory.
|
||||
@ -79,8 +81,8 @@ public final class EMF {
|
||||
log.info("Configuring EntityManager from environment parameters");
|
||||
Map<Object, Object> props = new HashMap<Object, Object>();
|
||||
props.put("hibernate.connection.driver_class", "org.h2.Driver");
|
||||
File dbDirectory = DirectoryUtil.getDbDirectory();
|
||||
String dbFile = dbDirectory.getAbsoluteFile() + File.separator + "docs";
|
||||
Path dbDirectory = DirectoryUtil.getDbDirectory();
|
||||
String dbFile = dbDirectory.resolve("docs").toAbsolutePath().toString();
|
||||
props.put("hibernate.connection.url", "jdbc:h2:file:" + dbFile + ";CACHE_SIZE=65536");
|
||||
props.put("hibernate.connection.username", "sa");
|
||||
props.put("hibernate.hbm2ddl.auto", "none");
|
||||
|
@ -1 +1 @@
|
||||
db.version=3
|
||||
db.version=4
|
@ -0,0 +1,3 @@
|
||||
alter table T_USER add column USE_STORAGEQUOTA_N bigint not null default 10000000000;
|
||||
alter table T_USER add column USE_STORAGECURRENT_N bigint not null default 0;
|
||||
update T_CONFIG set CFG_VALUE_C = '4' where CFG_ID_C = 'DB_VERSION';
|
@ -20,6 +20,8 @@ public class TestJpa extends BaseTransactionalTest {
|
||||
user.setUsername("username");
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setRoleId("admin");
|
||||
user.setStorageCurrent(0l);
|
||||
user.setStorageQuota(10l);
|
||||
user.setPrivateKey("AwesomePrivateKey");
|
||||
String id = userDao.create(user);
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
<org.apache.maven.plugins.maven-release-plugin.version>2.5.2</org.apache.maven.plugins.maven-release-plugin.version>
|
||||
<org.apache.maven.plugins.maven-resources-plugin.version>2.7</org.apache.maven.plugins.maven-resources-plugin.version>
|
||||
<org.apache.maven.plugins.maven-war-plugin.version>2.6</org.apache.maven.plugins.maven-war-plugin.version>
|
||||
<org.apache.maven.plugins.maven-surefire-plugin.version>2.18.1</org.apache.maven.plugins.maven-surefire-plugin.version>
|
||||
<org.eclipse.jetty.jetty-maven-plugin.version>9.2.13.v20150730</org.eclipse.jetty.jetty-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
@ -108,12 +109,17 @@
|
||||
<version>${org.apache.maven.plugins.maven-war-plugin.version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${org.apache.maven.plugins.maven-surefire-plugin.version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-maven-plugin</artifactId>
|
||||
<version>${org.eclipse.jetty.jetty-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -155,7 +155,23 @@ public class ValidationUtil {
|
||||
try {
|
||||
return Integer.valueOf(s);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ClientException("Validation Error", MessageFormat.format("{0} is not a number", name));
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a number", name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is a number.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @return Parsed number
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static Long validateLong(String s, String name) throws ClientException {
|
||||
try {
|
||||
return Long.valueOf(s);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a number", name));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.sismics.util.filter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
@ -44,20 +43,19 @@ public class RequestContextFilter implements Filter {
|
||||
if (!filterConfig.getServletContext().getServerInfo().startsWith("Grizzly")) {
|
||||
EnvironmentUtil.setWebappContext(true);
|
||||
}
|
||||
File baseDataDirectory = null;
|
||||
try {
|
||||
baseDataDirectory = DirectoryUtil.getBaseDataDirectory();
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(MessageFormat.format("Using base data directory: {0}", DirectoryUtil.getBaseDataDirectory()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing base data directory", e);
|
||||
}
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(MessageFormat.format("Using base data directory: {0}", baseDataDirectory.toString()));
|
||||
}
|
||||
|
||||
|
||||
// Initialize file logger
|
||||
RollingFileAppender fileAppender = new RollingFileAppender();
|
||||
fileAppender.setName("FILE");
|
||||
fileAppender.setFile(DirectoryUtil.getLogDirectory() + File.separator + "docs.log");
|
||||
fileAppender.setFile(DirectoryUtil.getLogDirectory().resolve("docs.log").toString());
|
||||
fileAppender.setLayout(new PatternLayout("%d{DATE} %p %l %m %n"));
|
||||
fileAppender.setThreshold(Level.INFO);
|
||||
fileAppender.setAppend(true);
|
||||
|
@ -1,8 +1,6 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import javax.ws.rs.core.Application;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
@ -63,8 +61,7 @@ public abstract class BaseJerseyTest extends JerseyTest {
|
||||
|
||||
clientUtil = new ClientUtil(target());
|
||||
|
||||
String httpRoot = URLDecoder.decode(new File(getClass().getResource("/").getFile()).getAbsolutePath(), "utf-8");
|
||||
httpServer = HttpServer.createSimpleServer(httpRoot, "localhost", getPort());
|
||||
httpServer = HttpServer.createSimpleServer(getClass().getResource("/").getFile(), "localhost", getPort());
|
||||
WebappContext context = new WebappContext("GrizzlyContext", "/docs");
|
||||
context.addFilter("requestContextFilter", RequestContextFilter.class)
|
||||
.addMappingForUrlPatterns(null, "/*");
|
||||
|
@ -40,7 +40,7 @@ public class ClientUtil {
|
||||
form.param("username", username);
|
||||
form.param("email", username + "@docs.com");
|
||||
form.param("password", "12345678");
|
||||
form.param("time_zone", "Asia/Tokyo");
|
||||
form.param("storage_quota", "1000000"); // 1MB quota
|
||||
resource.path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.put(Entity.form(form), JsonObject.class);
|
||||
|
@ -174,6 +174,15 @@
|
||||
</webApp>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<forkCount>1</forkCount>
|
||||
<reuseForks>false</reuseForks>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=3
|
||||
db.version=4
|
@ -1,5 +1,8 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -19,8 +22,10 @@ import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
@ -165,14 +170,66 @@ public class AppResource extends BaseResource {
|
||||
}
|
||||
|
||||
// Check if each stored file is valid
|
||||
java.io.File[] storedFileList = DirectoryUtil.getStorageDirectory().listFiles();
|
||||
for (java.io.File storedFile : storedFileList) {
|
||||
String fileName = storedFile.getName();
|
||||
try (DirectoryStream<java.nio.file.Path> storedFileList = Files.newDirectoryStream(DirectoryUtil.getStorageDirectory())) {
|
||||
for (java.nio.file.Path storedFile : storedFileList) {
|
||||
String fileName = storedFile.getFileName().toString();
|
||||
String[] fileNameArray = fileName.split("_");
|
||||
if (!fileMap.containsKey(fileNameArray[0])) {
|
||||
storedFile.delete();
|
||||
Files.delete(storedFile);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Error deleting orphan files", e);
|
||||
}
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recompute the quota for each user.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/recompute_quota")
|
||||
public Response batchRecomputeQuota() {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get all files
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
|
||||
// Count each file for the corresponding user quota
|
||||
UserDao userDao = new UserDao();
|
||||
Map<String, User> userMap = new HashMap<>();
|
||||
for (File file : fileList) {
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
User user = null;
|
||||
if (userMap.containsKey(file.getUserId())) {
|
||||
user = userMap.get(file.getUserId());
|
||||
} else {
|
||||
user = userDao.getById(file.getUserId());
|
||||
user.setStorageCurrent(0l);
|
||||
userMap.put(user.getId(), user);
|
||||
}
|
||||
|
||||
try {
|
||||
user.setStorageCurrent(user.getStorageCurrent() + Files.size(storedFile));
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("MissingFile", "File does not exist", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Save all users
|
||||
for (User user : userMap.values()) {
|
||||
userDao.update(user);
|
||||
}
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -123,6 +123,11 @@ public class FileResource extends BaseResource {
|
||||
throw new ClientException("InvalidFileType", "File type not recognized");
|
||||
}
|
||||
|
||||
// Validate quota
|
||||
if (user.getStorageCurrent() + fileData.length > user.getStorageQuota()) {
|
||||
throw new ClientException("QuotaReached", "Quota limit reached");
|
||||
}
|
||||
|
||||
try {
|
||||
// Get files of this document
|
||||
FileDao fileDao = new FileDao();
|
||||
@ -144,6 +149,10 @@ public class FileResource extends BaseResource {
|
||||
// Save the file
|
||||
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
||||
|
||||
// Update the user quota
|
||||
user.setStorageCurrent(user.getStorageCurrent() + fileData.length);
|
||||
userDao.update(user);
|
||||
|
||||
// Raise a new file created event if we have a document
|
||||
if (documentId != null) {
|
||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||
@ -156,7 +165,8 @@ public class FileResource extends BaseResource {
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok")
|
||||
.add("id", fileId);
|
||||
.add("id", fileId)
|
||||
.add("size", fileData.length);
|
||||
return Response.ok().entity(response.build()).build();
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("FileError", "Error adding a file", e);
|
||||
@ -206,8 +216,8 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Raise a new file created event (it wasn't sent during file creation)
|
||||
try {
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), id).toFile();
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
||||
InputStream fileInputStream = Files.newInputStream(storedFile);
|
||||
final InputStream responseInputStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey());
|
||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||
fileCreatedAsyncEvent.setDocument(document);
|
||||
@ -294,11 +304,16 @@ public class FileResource extends BaseResource {
|
||||
|
||||
JsonArrayBuilder files = Json.createArrayBuilder();
|
||||
for (File fileDb : fileList) {
|
||||
try {
|
||||
files.add(Json.createObjectBuilder()
|
||||
.add("id", fileDb.getId())
|
||||
.add("mimetype", fileDb.getMimeType())
|
||||
.add("document_id", JsonUtil.nullable(fileDb.getDocumentId()))
|
||||
.add("create_date", fileDb.getCreateDate().getTime()));
|
||||
.add("create_date", fileDb.getCreateDate().getTime())
|
||||
.add("size", Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId()))));
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Unable to get the size of " + fileDb.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
@ -341,6 +356,17 @@ public class FileResource extends BaseResource {
|
||||
// Delete the file
|
||||
fileDao.delete(file.getId());
|
||||
|
||||
// Update the user quota
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(principal.getId());
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
||||
try {
|
||||
user.setStorageCurrent(user.getStorageCurrent() - Files.size(storedFile));
|
||||
userDao.update(user);
|
||||
} catch (IOException e) {
|
||||
// The file doesn't exists on disk, which is weird, but not fatal
|
||||
}
|
||||
|
||||
// Raise a new file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
@ -396,30 +422,33 @@ public class FileResource extends BaseResource {
|
||||
|
||||
|
||||
// Get the stored file
|
||||
java.io.File storedfile;
|
||||
java.nio.file.Path storedFile;
|
||||
String mimeType;
|
||||
boolean decrypt = false;
|
||||
if (size != null) {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId + "_" + size).toFile();
|
||||
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_" + size);
|
||||
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
|
||||
decrypt = true; // Thumbnails are encrypted
|
||||
if (!storedfile.exists()) {
|
||||
storedfile = new java.io.File(getClass().getResource("/image/file.png").getFile());
|
||||
if (!Files.exists(storedFile)) {
|
||||
storedFile = Paths.get(getClass().getResource("/image/file.png").getFile());
|
||||
mimeType = MimeType.IMAGE_PNG;
|
||||
decrypt = false;
|
||||
}
|
||||
} else {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId).toFile();
|
||||
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
|
||||
mimeType = file.getMimeType();
|
||||
decrypt = true; // Original files are encrypted
|
||||
}
|
||||
|
||||
// Stream the output and decrypt it if necessary
|
||||
StreamingOutput stream;
|
||||
|
||||
// A file is always encrypted by the creator of it
|
||||
User user = userDao.getById(file.getUserId());
|
||||
|
||||
// Write the decrypted file to the output
|
||||
try {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
InputStream fileInputStream = Files.newInputStream(storedFile);
|
||||
final InputStream responseInputStream = decrypt ?
|
||||
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
|
||||
|
||||
@ -484,8 +513,8 @@ public class FileResource extends BaseResource {
|
||||
// Add each file to the ZIP stream
|
||||
int index = 0;
|
||||
for (File file : fileList) {
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
java.nio.file.Path storedfile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
InputStream fileInputStream = Files.newInputStream(storedfile);
|
||||
|
||||
// Add the decrypted file to the ZIP stream
|
||||
// Files are encrypted by the creator of them
|
||||
|
@ -64,7 +64,8 @@ public class UserResource extends BaseResource {
|
||||
public Response register(
|
||||
@FormParam("username") String username,
|
||||
@FormParam("password") String password,
|
||||
@FormParam("email") String email) {
|
||||
@FormParam("email") String email,
|
||||
@FormParam("storage_quota") String storageQuotaStr) {
|
||||
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
@ -76,6 +77,7 @@ public class UserResource extends BaseResource {
|
||||
ValidationUtil.validateAlphanumeric(username, "username");
|
||||
password = ValidationUtil.validateLength(password, "password", 8, 50);
|
||||
email = ValidationUtil.validateLength(email, "email", 3, 50);
|
||||
Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
|
||||
ValidationUtil.validateEmail(email, "email");
|
||||
|
||||
// Create the user
|
||||
@ -84,6 +86,8 @@ public class UserResource extends BaseResource {
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setEmail(email);
|
||||
user.setStorageQuota(storageQuota);
|
||||
user.setStorageCurrent(0l);
|
||||
try {
|
||||
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
@ -135,9 +139,9 @@ public class UserResource extends BaseResource {
|
||||
if (email != null) {
|
||||
user.setEmail(email);
|
||||
}
|
||||
|
||||
user = userDao.update(user);
|
||||
|
||||
// Change the password
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
user.setPassword(password);
|
||||
userDao.updatePassword(user);
|
||||
@ -162,7 +166,8 @@ public class UserResource extends BaseResource {
|
||||
public Response update(
|
||||
@PathParam("username") String username,
|
||||
@FormParam("password") String password,
|
||||
@FormParam("email") String email) {
|
||||
@FormParam("email") String email,
|
||||
@FormParam("storage_quota") String storageQuotaStr) {
|
||||
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
@ -184,11 +189,14 @@ public class UserResource extends BaseResource {
|
||||
if (email != null) {
|
||||
user.setEmail(email);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(storageQuotaStr)) {
|
||||
Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
|
||||
user.setStorageQuota(storageQuota);
|
||||
}
|
||||
user = userDao.update(user);
|
||||
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
// Change the password
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
user.setPassword(password);
|
||||
userDao.updatePassword(user);
|
||||
}
|
||||
@ -406,7 +414,9 @@ public class UserResource extends BaseResource {
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(principal.getId());
|
||||
response.add("username", user.getUsername())
|
||||
.add("email", user.getEmail());
|
||||
.add("email", user.getEmail())
|
||||
.add("storage_quota", user.getStorageQuota())
|
||||
.add("storage_current", user.getStorageCurrent());
|
||||
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
|
||||
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
|
||||
baseFunctions.add(baseFunction);
|
||||
@ -441,7 +451,9 @@ public class UserResource extends BaseResource {
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("username", user.getUsername())
|
||||
.add("email", user.getEmail());
|
||||
.add("email", user.getEmail())
|
||||
.add("storage_quota", user.getStorageQuota())
|
||||
.add("storage_current", user.getStorageCurrent());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
@ -477,6 +489,8 @@ public class UserResource extends BaseResource {
|
||||
.add("id", userDto.getId())
|
||||
.add("username", userDto.getUsername())
|
||||
.add("email", userDto.getEmail())
|
||||
.add("storage_quota", userDto.getStorageQuota())
|
||||
.add("storage_current", userDto.getStorageCurrent())
|
||||
.add("create_date", userDto.getCreateTimestamp()));
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Document default controller.
|
||||
*/
|
||||
angular.module('docs').controller('DocumentDefault', function($scope, $state, Restangular, $upload) {
|
||||
angular.module('docs').controller('DocumentDefault', function($scope, $rootScope, $state, Restangular, $upload) {
|
||||
// Load app data
|
||||
Restangular.one('app').get().then(function(data) {
|
||||
$scope.app = data;
|
||||
@ -73,7 +73,18 @@ angular.module('docs').controller('DocumentDefault', function($scope, $state, Re
|
||||
newfile.progress = parseInt(100.0 * e.loaded / e.total);
|
||||
})
|
||||
.success(function (data) {
|
||||
// Update local model with real data
|
||||
newfile.id = data.id;
|
||||
newfile.size = data.size;
|
||||
|
||||
// New file uploaded, increase used quota
|
||||
$rootScope.userInfo.storage_current += data.size;
|
||||
})
|
||||
.error(function (data) {
|
||||
newfile.status = 'Upload error';
|
||||
if (data.type == 'QuotaReached') {
|
||||
newfile.status += ' - Quota reached';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -90,7 +101,11 @@ angular.module('docs').controller('DocumentDefault', function($scope, $state, Re
|
||||
$scope.deleteFile = function ($event, file) {
|
||||
$event.stopPropagation();
|
||||
|
||||
Restangular.one('file', file.id).remove().then(function () {
|
||||
Restangular.one('file', file.id).remove().then(function() {
|
||||
// File deleted, decrease used quota
|
||||
$rootScope.userInfo.storage_current -= file.size;
|
||||
|
||||
// Update local data
|
||||
$scope.loadFiles();
|
||||
});
|
||||
return false;
|
||||
|
@ -142,8 +142,8 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
|
||||
success: function(response) {
|
||||
deferred.resolve(response);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
deferred.reject(errorThrown);
|
||||
error: function(jqXHR) {
|
||||
deferred.reject(jqXHR);
|
||||
},
|
||||
xhr: function() {
|
||||
var myXhr = $.ajaxSettings.xhr();
|
||||
@ -155,8 +155,23 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
|
||||
|
||||
// Update progress bar and title on progress
|
||||
var startProgress = $scope.fileProgress;
|
||||
deferred.promise.then(null, null, function(e) {
|
||||
var done = 1 - (e.total - e.position) / e.total;
|
||||
deferred.promise.then(function(data) {
|
||||
// New file uploaded, increase used quota
|
||||
$rootScope.userInfo.storage_current += data.size;
|
||||
}, function(data) {
|
||||
// Error uploading a file, we stop here
|
||||
$scope.alerts.unshift({
|
||||
type: 'danger',
|
||||
msg: 'Document successfully ' + ($scope.isEdit() ? 'edited' : 'added') + ' but some files cannot be uploaded'
|
||||
+ (data.responseJSON.type == 'QuotaReached' ? ' - Quota reached' : '')
|
||||
});
|
||||
|
||||
// Reset view and title
|
||||
$scope.fileIsUploading = false;
|
||||
$scope.fileProgress = 0;
|
||||
$rootScope.pageTitle = 'Sismics Docs';
|
||||
}, function(e) {
|
||||
var done = 1 - (e.total - e.loaded) / e.total;
|
||||
var chunk = 100 / _.size($scope.newFiles);
|
||||
$scope.fileProgress = startProgress + done * chunk;
|
||||
$rootScope.pageTitle = Math.round($scope.fileProgress) + '% - Sismics Docs';
|
||||
@ -170,7 +185,7 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
|
||||
var then = function() {
|
||||
key++;
|
||||
if ($scope.newFiles[key]) {
|
||||
sendFile(key).then(then); // TODO Handle upload error
|
||||
sendFile(key).then(then);
|
||||
} else {
|
||||
$scope.fileIsUploading = false;
|
||||
$scope.fileProgress = 0;
|
||||
|
@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Document view content controller.
|
||||
*/
|
||||
angular.module('docs').controller('DocumentViewContent', function ($scope, $stateParams, Restangular, $dialog, $state, $upload) {
|
||||
angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, $upload) {
|
||||
/**
|
||||
* Configuration for file sorting.
|
||||
*/
|
||||
@ -55,6 +55,10 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $stat
|
||||
$dialog.messageBox(title, msg, btns, function (result) {
|
||||
if (result == 'ok') {
|
||||
Restangular.one('file', file.id).remove().then(function () {
|
||||
// File deleted, decrease used quota
|
||||
$rootScope.userInfo.storage_current -= file.size;
|
||||
|
||||
// Update local data
|
||||
$scope.loadFiles();
|
||||
});
|
||||
}
|
||||
@ -109,11 +113,22 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $stat
|
||||
id: $stateParams.id
|
||||
}
|
||||
})
|
||||
.progress(function (e) {
|
||||
.progress(function(e) {
|
||||
newfile.progress = parseInt(100.0 * e.loaded / e.total);
|
||||
})
|
||||
.success(function (data) {
|
||||
.success(function(data) {
|
||||
// Update local model with real data
|
||||
newfile.id = data.id;
|
||||
newfile.size = data.size;
|
||||
|
||||
// New file uploaded, increase used quota
|
||||
$rootScope.userInfo.storage_current += data.size;
|
||||
})
|
||||
.error(function (data) {
|
||||
newfile.status = 'Upload error';
|
||||
if (data.type == 'QuotaReached') {
|
||||
newfile.status += ' - Quota reached';
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
@ -16,6 +16,7 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
|
||||
*/
|
||||
if ($scope.isEdit()) {
|
||||
Restangular.one('user', $stateParams.username).get().then(function(data) {
|
||||
data.storage_quota /= 1000000;
|
||||
$scope.user = data;
|
||||
});
|
||||
}
|
||||
@ -25,15 +26,17 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
|
||||
*/
|
||||
$scope.edit = function() {
|
||||
var promise = null;
|
||||
var user = angular.copy($scope.user);
|
||||
user.storage_quota *= 1000000;
|
||||
|
||||
if ($scope.isEdit()) {
|
||||
promise = Restangular
|
||||
.one('user', $stateParams.username)
|
||||
.post('', $scope.user);
|
||||
.post('', user);
|
||||
} else {
|
||||
promise = Restangular
|
||||
.one('user')
|
||||
.put($scope.user);
|
||||
.put(user);
|
||||
}
|
||||
|
||||
promise.then(function() {
|
||||
|
@ -71,7 +71,7 @@
|
||||
<!-- endref -->
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default" role="navigation" ng-controller="Navigation">
|
||||
<nav class="navbar navbar-inverse" role="navigation" ng-controller="Navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle"
|
||||
ng-init="isCollapsed = true"
|
||||
@ -110,7 +110,10 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#/settings/account" title="Logged in as {{ userInfo.username }}">{{ userInfo.username }}</a>
|
||||
<a href="#/settings/account" title="Logged in as {{ userInfo.username }}">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
{{ userInfo.username }}
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: $uiRoute}" ui-route="/settings.*">
|
||||
<a href="#/settings/account">
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div ng-show="app">
|
||||
<div class="well">
|
||||
<h3>Quick upload</h3>
|
||||
<h3><span class="glyphicon glyphicon-cloud-upload"></span> Quick upload</h3>
|
||||
<div class="row upload-zone" ng-model="dropFiles" ng-file-drop drag-over-class="bg-success"
|
||||
ng-multiple="true" allow-dir="false" accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
@ -44,8 +44,8 @@
|
||||
|
||||
<div ui-view="file"></div>
|
||||
|
||||
<div class="well">
|
||||
<h3>Latest activity</h3>
|
||||
<div>
|
||||
<h3><span class="glyphicon glyphicon-tasks"></span> Latest activity</h3>
|
||||
<audit-log logs="logs" />
|
||||
</div>
|
||||
|
||||
|
@ -73,6 +73,11 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pull-left" title="To upgrade your quota, ask your administrator">
|
||||
{{ userInfo.storage_current / 1000000 | number: 0 }}MB ({{ userInfo.storage_current / userInfo.storage_quota * 100 | number: 1 }}%)
|
||||
used on {{ userInfo.storage_quota / 1000000 | number: 0 }}MB
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
{{ totalDocuments }} document{{ totalDocuments > 1 ? 's' : '' }} found
|
||||
</div>
|
||||
|
@ -37,6 +37,21 @@
|
||||
<span class="help-block" ng-show="editUserForm.email.$error.maxlength">Too long</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': !editUserForm.storage_quota.$valid, success: editUserForm.storage_quota.$valid }">
|
||||
<label class="col-sm-2 control-label" for="inputQuota">Storage quota</label>
|
||||
|
||||
<div class="col-sm-7">
|
||||
<div class="input-group">
|
||||
<input name="storage_quota" type="number" id="inputQuota" required class="form-control"
|
||||
ng-pattern="/[0-9]*/" placeholder="Storage quota (in MB)" ng-model="user.storage_quota"/>
|
||||
<div class="input-group-addon">MB</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3">
|
||||
<span class="help-block" ng-show="editUserForm.storage_quota.$error.pattern">Number required</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': !editUserForm.password.$valid, success: editUserForm.password.$valid }">
|
||||
<label class="col-sm-2 control-label" for="inputPassword">Password</label>
|
||||
|
||||
|
@ -7,6 +7,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Navbar color
|
||||
.navbar {
|
||||
background-color: #263238;
|
||||
}
|
||||
|
||||
// Documents list
|
||||
.table-documents {
|
||||
thead th {
|
||||
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=3
|
||||
db.version=4
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=3
|
||||
db.version=4
|
@ -53,6 +53,12 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.post(Entity.form(new Form()));
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Recompute quota
|
||||
response = target().path("/app/batch/recompute_quota").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.post(Entity.form(new Form()));
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.json.JsonArray;
|
||||
@ -237,9 +236,9 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check that the associated files are deleted from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
||||
java.io.File thumbnailFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_thumb").toFile();
|
||||
java.io.File storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id).toFile();
|
||||
java.io.File webFile = DirectoryUtil.getStorageDirectory().resolve(file1Id + "_web").toFile();
|
||||
java.io.File thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(file1Id + "_thumb").toFile();
|
||||
Assert.assertFalse(storedFile.exists());
|
||||
Assert.assertFalse(webFile.exists());
|
||||
Assert.assertFalse(thumbnailFile.exists());
|
||||
|
@ -1,9 +1,9 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.json.JsonArray;
|
||||
@ -68,6 +68,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class);
|
||||
file1Id = json.getString("id");
|
||||
Assert.assertNotNull(file1Id);
|
||||
Assert.assertEquals(163510l, json.getJsonNumber("size").longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,8 +122,8 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(fileBytes.length > 0);
|
||||
|
||||
// Check that the files are not readable directly from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
try (InputStream storedFileInputStream = new BufferedInputStream(new FileInputStream(storedFile))) {
|
||||
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id);
|
||||
try (InputStream storedFileInputStream = new BufferedInputStream(Files.newInputStream(storedFile))) {
|
||||
Assert.assertNull(MimeTypeUtil.guessMimeType(storedFileInputStream));
|
||||
}
|
||||
|
||||
@ -135,6 +136,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
JsonArray files = json.getJsonArray("files");
|
||||
Assert.assertEquals(2, files.size());
|
||||
Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id"));
|
||||
Assert.assertEquals(163510l, files.getJsonObject(0).getJsonNumber("size").longValue());
|
||||
Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id"));
|
||||
|
||||
// Reorder files
|
||||
@ -179,12 +181,12 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.NOT_FOUND, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Check that files are deleted from FS
|
||||
storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
||||
java.io.File thumbnailFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_thumb").toFile();
|
||||
Assert.assertFalse(storedFile.exists());
|
||||
Assert.assertFalse(webFile.exists());
|
||||
Assert.assertFalse(thumbnailFile.exists());
|
||||
storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id);
|
||||
Path webFile = DirectoryUtil.getStorageDirectory().resolve(file1Id + "_web");
|
||||
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(file1Id + "_thumb");
|
||||
Assert.assertFalse(Files.exists(storedFile));
|
||||
Assert.assertFalse(Files.exists(webFile));
|
||||
Assert.assertFalse(Files.exists(thumbnailFile));
|
||||
|
||||
// Get all files from a document
|
||||
json = target().path("/file/list")
|
||||
@ -198,7 +200,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
|
||||
@Test
|
||||
public void testOrphanFile() throws Exception {
|
||||
// Login file1
|
||||
// Login file2
|
||||
clientUtil.createUser("file2");
|
||||
String file2AuthenticationToken = clientUtil.login("file2");
|
||||
|
||||
@ -280,4 +282,97 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
.delete(JsonObject.class);
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuota() throws Exception {
|
||||
// Login file_quota
|
||||
clientUtil.createUser("file_quota");
|
||||
String fileQuotaAuthenticationToken = clientUtil.login("file_quota");
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
String file1Id = null;
|
||||
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
|
||||
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
|
||||
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
|
||||
JsonObject json = target()
|
||||
.register(MultiPartFeature.class)
|
||||
.path("/file").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.put(Entity.entity(multiPart.bodyPart(streamDataBodyPart),
|
||||
MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class);
|
||||
file1Id = json.getString("id");
|
||||
Assert.assertNotNull(file1Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Check current quota
|
||||
JsonObject json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(292641l, json.getJsonNumber("storage_current").longValue());
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
|
||||
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
|
||||
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
|
||||
target()
|
||||
.register(MultiPartFeature.class)
|
||||
.path("/file").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.put(Entity.entity(multiPart.bodyPart(streamDataBodyPart),
|
||||
MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class);
|
||||
}
|
||||
}
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(585282l, json.getJsonNumber("storage_current").longValue());
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
|
||||
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
|
||||
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
|
||||
target()
|
||||
.register(MultiPartFeature.class)
|
||||
.path("/file").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.put(Entity.entity(multiPart.bodyPart(streamDataBodyPart),
|
||||
MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class);
|
||||
}
|
||||
}
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(877923l, json.getJsonNumber("storage_current").longValue());
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
|
||||
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
|
||||
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
|
||||
Response response = target()
|
||||
.register(MultiPartFeature.class)
|
||||
.path("/file").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.put(Entity.entity(multiPart.bodyPart(streamDataBodyPart),
|
||||
MediaType.MULTIPART_FORM_DATA_TYPE));
|
||||
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes a file
|
||||
json = target().path("/file/" + file1Id).request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.delete(JsonObject.class);
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaAuthenticationToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(585282l, json.getJsonNumber("storage_current").longValue());
|
||||
}
|
||||
}
|
@ -48,6 +48,13 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.get(JsonObject.class);
|
||||
JsonArray users = json.getJsonArray("users");
|
||||
Assert.assertTrue(users.size() > 0);
|
||||
JsonObject user = users.getJsonObject(0);
|
||||
Assert.assertNotNull(user.getString("id"));
|
||||
Assert.assertNotNull(user.getString("username"));
|
||||
Assert.assertNotNull(user.getString("email"));
|
||||
Assert.assertNotNull(user.getJsonNumber("storage_quota"));
|
||||
Assert.assertNotNull(user.getJsonNumber("storage_current"));
|
||||
Assert.assertNotNull(user.getJsonNumber("create_date"));
|
||||
|
||||
// Create a user KO (login length validation)
|
||||
Response response = target().path("/user").request()
|
||||
@ -55,7 +62,8 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.put(Entity.form(new Form()
|
||||
.param("username", " bb ")
|
||||
.param("email", "bob@docs.com")
|
||||
.param("password", "12345678")));
|
||||
.param("password", "12345678")
|
||||
.param("storage_quota", "10")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ValidationError", json.getString("type"));
|
||||
@ -67,19 +75,34 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.put(Entity.form(new Form()
|
||||
.param("username", "bob-")
|
||||
.param("email", "bob@docs.com")
|
||||
.param("password", "12345678")));
|
||||
.param("password", "12345678")
|
||||
.param("storage_quota", "10")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ValidationError", json.getString("type"));
|
||||
Assert.assertTrue(json.getString("message"), json.getString("message").contains("alphanumeric"));
|
||||
|
||||
// Create a user KO (invalid quota)
|
||||
response = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.put(Entity.form(new Form()
|
||||
.param("username", "bob")
|
||||
.param("email", "bob@docs.com")
|
||||
.param("password", "12345678")
|
||||
.param("storage_quota", "nope")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ValidationError", json.getString("type"));
|
||||
Assert.assertTrue(json.getString("message"), json.getString("message").contains("number"));
|
||||
|
||||
// Create a user KO (email format validation)
|
||||
response = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.put(Entity.form(new Form()
|
||||
.param("username", "bob")
|
||||
.param("email", "bobdocs.com")
|
||||
.param("password", "12345678")));
|
||||
.param("password", "12345678")
|
||||
.param("storage_quota", "10")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ValidationError", json.getString("type"));
|
||||
@ -89,7 +112,8 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
Form form = new Form()
|
||||
.param("username", " bob ")
|
||||
.param("email", " bob@docs.com ")
|
||||
.param("password", " 12345678 ");
|
||||
.param("password", " 12345678 ")
|
||||
.param("storage_quota", "10");
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.put(Entity.form(form), JsonObject.class);
|
||||
@ -154,6 +178,8 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals("alice@docs.com", json.getString("email"));
|
||||
Assert.assertFalse(json.getBoolean("is_default_password"));
|
||||
Assert.assertEquals(0l, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(1000000l, json.getJsonNumber("storage_quota").longValue());
|
||||
|
||||
// Check bob user information
|
||||
json = target().path("/user").request()
|
||||
@ -219,6 +245,8 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertTrue(json.getBoolean("is_default_password"));
|
||||
Assert.assertEquals(0l, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(10000000000l, json.getJsonNumber("storage_quota").longValue());
|
||||
|
||||
// User admin updates his information
|
||||
json = target().path("/user").request()
|
||||
|
Loading…
Reference in New Issue
Block a user