mirror of
https://github.com/sismics/docs.git
synced 2025-12-14 10:16:21 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60beb0e2f5 | ||
|
|
77f0368ba5 | ||
|
|
596dd4db13 | ||
|
|
d2ba291287 | ||
|
|
01cb3e611c | ||
|
|
bd9e918a62 | ||
|
|
56f6038c09 | ||
|
|
f29ed3e671 | ||
|
|
726121d8c8 | ||
|
|
c40e7e1cc9 | ||
|
|
b1f9b072f3 | ||
|
|
22cea20a90 | ||
|
|
8eb5b8066e | ||
|
|
e37eab3b7a | ||
|
|
1db54174d4 | ||
|
|
d5fa3a4e3a | ||
|
|
fc53758eb7 | ||
|
|
f5079e83cb | ||
|
|
b399e4081f | ||
|
|
a80bc27582 | ||
|
|
eb57af4029 | ||
|
|
ac3580fb4a | ||
|
|
6e3d2ea972 | ||
|
|
870a44da0d | ||
|
|
62a5840777 | ||
|
|
a858699391 | ||
|
|
fb0aec5fee | ||
|
|
1a495ac948 | ||
|
|
ba083fb57a | ||
|
|
4e22111f38 | ||
|
|
db7a9f0e4a | ||
|
|
906de329ae | ||
|
|
00b00f0d0c | ||
|
|
0bc658a396 | ||
|
|
464d43194b | ||
|
|
1c606ebf25 | ||
|
|
504c4dd815 | ||
|
|
ecb5a7abf6 | ||
|
|
cc6d599293 |
10
README.md
10
README.md
@@ -14,10 +14,11 @@ Features
|
||||
--------
|
||||
|
||||
- Responsive user interface
|
||||
- Optical characted recognition
|
||||
- Optical character recognition
|
||||
- Support image and PDF files
|
||||
- Flexible search engine
|
||||
- Full text search in image and PDF
|
||||
- SHA-256 encryption
|
||||
- Tag system
|
||||
- Multi-users
|
||||
- Document sharing
|
||||
@@ -26,13 +27,13 @@ Features
|
||||
License
|
||||
-------
|
||||
|
||||
Reader is released under the terms of the GPL license. See `COPYING` for more
|
||||
Docs is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
|
||||
How to build Docs from the sources
|
||||
------------------------------------
|
||||
----------------------------------
|
||||
|
||||
Prerequisites: JDK 7, Maven 3
|
||||
Prerequisites: JDK 7, Maven 3, Tesseract 3.02
|
||||
|
||||
Docs is organized in several Maven modules:
|
||||
|
||||
@@ -48,6 +49,7 @@ or download the sources from GitHub.
|
||||
|
||||
From the `docs-parent` directory:
|
||||
|
||||
mvn -Pinit validate -N
|
||||
mvn clean -DskipTests install
|
||||
|
||||
#### Run a stand-alone version
|
||||
|
||||
4
docs-android/.gitignore
vendored
Normal file
4
docs-android/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
1
docs-android/app/.gitignore
vendored
Normal file
1
docs-android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
76
docs-android/app/app.iml
Normal file
76
docs-android/app/app.iml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
|
||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/classes/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" type="java-test-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
40
docs-android/app/build.gradle
Normal file
40
docs-android/app/build.gradle
Normal file
@@ -0,0 +1,40 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.7.+'
|
||||
}
|
||||
}
|
||||
apply plugin: 'android'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "19.0.0"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 19
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
runProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:18.0.0'
|
||||
}
|
||||
17
docs-android/app/proguard-rules.txt
Normal file
17
docs-android/app/proguard-rules.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /opt/android-studio/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
29
docs-android/app/src/main/AndroidManifest.xml
Normal file
29
docs-android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.sismics.docs" >
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocListActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocDetailActivity"
|
||||
android:label="@string/title_doc_detail"
|
||||
android:parentActivityName="com.sismics.docs.DocListActivity" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.sismics.docs.DocListActivity" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.MenuItem;
|
||||
|
||||
/**
|
||||
* An activity representing a single Doc detail screen. This
|
||||
* activity is only used on handset devices. On tablet-size devices,
|
||||
* item details are presented side-by-side with a list of items
|
||||
* in a {@link DocListActivity}.
|
||||
* <p>
|
||||
* This activity is mostly just a 'shell' activity containing nothing
|
||||
* more than a {@link DocDetailFragment}.
|
||||
*/
|
||||
public class DocDetailActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_doc_detail);
|
||||
|
||||
// Show the Up button in the action bar.
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// savedInstanceState is non-null when there is fragment state
|
||||
// saved from previous configurations of this activity
|
||||
// (e.g. when rotating the screen from portrait to landscape).
|
||||
// In this case, the fragment will automatically be re-added
|
||||
// to its container so we don't need to manually add it.
|
||||
// For more information, see the Fragments API guide at:
|
||||
//
|
||||
// http://developer.android.com/guide/components/fragments.html
|
||||
//
|
||||
if (savedInstanceState == null) {
|
||||
// Create the detail fragment and add it to the activity
|
||||
// using a fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID,
|
||||
getIntent().getStringExtra(DocDetailFragment.ARG_ITEM_ID));
|
||||
DocDetailFragment fragment = new DocDetailFragment();
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.doc_detail_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
NavUtils.navigateUpTo(this, new Intent(this, DocListActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A fragment representing a single Doc detail screen.
|
||||
* This fragment is either contained in a {@link DocListActivity}
|
||||
* in two-pane mode (on tablets) or a {@link DocDetailActivity}
|
||||
* on handsets.
|
||||
*/
|
||||
public class DocDetailFragment extends Fragment {
|
||||
/**
|
||||
* The fragment argument representing the item ID that this fragment
|
||||
* represents.
|
||||
*/
|
||||
public static final String ARG_ITEM_ID = "item_id";
|
||||
|
||||
/**
|
||||
* The dummy content this fragment is presenting.
|
||||
*/
|
||||
private DummyContent.DummyItem mItem;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public DocDetailFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
||||
// Load the dummy content specified by the fragment
|
||||
// arguments. In a real-world scenario, use a Loader
|
||||
// to load content from a content provider.
|
||||
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_doc_detail, container, false);
|
||||
|
||||
// Show the dummy content as text in a TextView.
|
||||
if (mItem != null) {
|
||||
((TextView) rootView.findViewById(R.id.doc_detail)).setText(mItem.content);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
|
||||
/**
|
||||
* An activity representing a list of Docs. This activity
|
||||
* has different presentations for handset and tablet-size devices. On
|
||||
* handsets, the activity presents a list of items, which when touched,
|
||||
* lead to a {@link DocDetailActivity} representing
|
||||
* item details. On tablets, the activity presents the list of items and
|
||||
* item details side-by-side using two vertical panes.
|
||||
* <p>
|
||||
* The activity makes heavy use of fragments. The list of items is a
|
||||
* {@link DocListFragment} and the item details
|
||||
* (if present) is a {@link DocDetailFragment}.
|
||||
* <p>
|
||||
* This activity also implements the required
|
||||
* {@link DocListFragment.Callbacks} interface
|
||||
* to listen for item selections.
|
||||
*/
|
||||
public class DocListActivity extends FragmentActivity
|
||||
implements DocListFragment.Callbacks {
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_doc_list);
|
||||
|
||||
if (findViewById(R.id.doc_detail_container) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
((DocListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.doc_list))
|
||||
.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
// TODO: If exposing deep links into your app, handle intents here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method from {@link DocListFragment.Callbacks}
|
||||
* indicating that the item with the given ID was selected.
|
||||
*/
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID, id);
|
||||
DocDetailFragment fragment = new DocDetailFragment();
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.doc_detail_container, fragment)
|
||||
.commit();
|
||||
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, DocDetailActivity.class);
|
||||
detailIntent.putExtra(DocDetailFragment.ARG_ITEM_ID, id);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.sismics.docs.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A list fragment representing a list of Docs. This fragment
|
||||
* also supports tablet devices by allowing list items to be given an
|
||||
* 'activated' state upon selection. This helps indicate which item is
|
||||
* currently being viewed in a {@link DocDetailFragment}.
|
||||
* <p>
|
||||
* Activities containing this fragment MUST implement the {@link Callbacks}
|
||||
* interface.
|
||||
*/
|
||||
public class DocListFragment extends ListFragment {
|
||||
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
/**
|
||||
* The fragment's current callback object, which is notified of list item
|
||||
* clicks.
|
||||
*/
|
||||
private Callbacks mCallbacks = sDummyCallbacks;
|
||||
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
|
||||
/**
|
||||
* A callback interface that all activities containing this fragment must
|
||||
* implement. This mechanism allows activities to be notified of item
|
||||
* selections.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
/**
|
||||
* Callback for when an item has been selected.
|
||||
*/
|
||||
public void onItemSelected(String id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy implementation of the {@link Callbacks} interface that does
|
||||
* nothing. Used only when this fragment is not attached to an activity.
|
||||
*/
|
||||
private static Callbacks sDummyCallbacks = new Callbacks() {
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public DocListFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// TODO: replace with a real list adapter.
|
||||
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
|
||||
getActivity(),
|
||||
android.R.layout.simple_list_item_activated_1,
|
||||
android.R.id.text1,
|
||||
DummyContent.ITEMS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// Activities containing this fragment must implement its callbacks.
|
||||
if (!(activity instanceof Callbacks)) {
|
||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
||||
}
|
||||
|
||||
mCallbacks = (Callbacks) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
// Reset the active callbacks interface to the dummy implementation.
|
||||
mCallbacks = sDummyCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
super.onListItemClick(listView, view, position, id);
|
||||
|
||||
// Notify the active callbacks interface (the activity, if the
|
||||
// fragment is attached to one) that an item has been selected.
|
||||
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(activateOnItemClick
|
||||
? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sismics.docs.dummy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for providing sample content for user interfaces created by
|
||||
* Android template wizards.
|
||||
* <p>
|
||||
* TODO: Replace all uses of this class before publishing your app.
|
||||
*/
|
||||
public class DummyContent {
|
||||
|
||||
/**
|
||||
* An array of sample (dummy) items.
|
||||
*/
|
||||
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
||||
|
||||
/**
|
||||
* A map of sample (dummy) items, by ID.
|
||||
*/
|
||||
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
||||
|
||||
static {
|
||||
// Add 3 sample items.
|
||||
addItem(new DummyItem("1", "Item 1"));
|
||||
addItem(new DummyItem("2", "Item 2"));
|
||||
addItem(new DummyItem("3", "Item 3"));
|
||||
}
|
||||
|
||||
private static void addItem(DummyItem item) {
|
||||
ITEMS.add(item);
|
||||
ITEM_MAP.put(item.id, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy item representing a piece of content.
|
||||
*/
|
||||
public static class DummyItem {
|
||||
public String id;
|
||||
public String content;
|
||||
|
||||
public DummyItem(String id, String content) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
docs-android/app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs-android/app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docs-android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
docs-android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.sismics.docs.DocDetailActivity"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
10
docs-android/app/src/main/res/layout/activity_doc_list.xml
Normal file
10
docs-android/app/src/main/res/layout/activity_doc_list.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_list"
|
||||
android:name="com.sismics.docs.DocListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:context="com.sismics.docs.DocListActivity"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
@@ -0,0 +1,38 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:baselineAligned="false"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
tools:context="com.sismics.docs.DocListActivity">
|
||||
|
||||
<!--
|
||||
This layout is a two-pane layout for the Docs
|
||||
master/detail flow. See res/values-large/refs.xml and
|
||||
res/values-sw600dp/refs.xml for an example of layout aliases
|
||||
that replace the single-pane version of the layout with
|
||||
this two-pane version.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
|
||||
<fragment
|
||||
android:id="@+id/doc_list"
|
||||
android:name="com.sismics.docs.DocListFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/doc_detail_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_detail"
|
||||
style="?android:attr/textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:textIsSelectable="true"
|
||||
tools:context="com.sismics.docs.DocDetailFragment" />
|
||||
10
docs-android/app/src/main/res/values-large/refs.xml
Normal file
10
docs-android/app/src/main/res/values-large/refs.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on Large screens.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
||||
</resources>
|
||||
10
docs-android/app/src/main/res/values-sw600dp/refs.xml
Normal file
10
docs-android/app/src/main/res/values-sw600dp/refs.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on Large screens.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
||||
</resources>
|
||||
7
docs-android/app/src/main/res/values/strings.xml
Normal file
7
docs-android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Sismics Docs</string>
|
||||
<string name="title_doc_detail">Doc Detail</string>
|
||||
|
||||
</resources>
|
||||
8
docs-android/app/src/main/res/values/styles.xml
Normal file
8
docs-android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
1
docs-android/build.gradle
Normal file
1
docs-android/build.gradle
Normal file
@@ -0,0 +1 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
18
docs-android/gradle.properties
Normal file
18
docs-android/gradle.properties
Normal file
@@ -0,0 +1,18 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Settings specified in this file will override any Gradle settings
|
||||
# configured through the IDE.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
BIN
docs-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
docs-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
docs-android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
docs-android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
|
||||
164
docs-android/gradlew
vendored
Normal file
164
docs-android/gradlew
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
docs-android/gradlew.bat
vendored
Normal file
90
docs-android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
1
docs-android/settings.gradle
Normal file
1
docs-android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
@@ -122,6 +122,11 @@
|
||||
<artifactId>pdfbox</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
@@ -133,11 +138,6 @@
|
||||
<artifactId>imageio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -138,14 +138,15 @@ public class DocumentDao {
|
||||
*
|
||||
* @param paginatedList List of documents (updated by side effects)
|
||||
* @param criteria Search criteria
|
||||
* @return List of document
|
||||
* @return List of documents
|
||||
* @throws Exception
|
||||
*/
|
||||
public void findByCriteria(PaginatedList<DocumentDto> paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) throws Exception {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, s.SHA_ID_C is not null c5 ");
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, s.SHA_ID_C is not null c5, ");
|
||||
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6 ");
|
||||
sb.append(" from T_DOCUMENT d ");
|
||||
sb.append(" left join T_SHARE s on s.SHA_IDDOCUMENT_C = d.DOC_ID_C and s.SHA_DELETEDATE_D is null ");
|
||||
|
||||
@@ -211,6 +212,7 @@ public class DocumentDao {
|
||||
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
documentDto.setLanguage((String) o[i++]);
|
||||
documentDto.setShared((Boolean) o[i++]);
|
||||
documentDto.setFileCount(((Number) o[i++]).intValue());
|
||||
documentDtoList.add(documentDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ public class DocumentDto {
|
||||
*/
|
||||
private Boolean shared;
|
||||
|
||||
/**
|
||||
* File count.
|
||||
*/
|
||||
private Integer fileCount;
|
||||
|
||||
/**
|
||||
* Getter de id.
|
||||
*
|
||||
@@ -146,4 +151,20 @@ public class DocumentDto {
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of fileCount.
|
||||
* @return fileCount
|
||||
*/
|
||||
public Integer getFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of fileCount.
|
||||
* @param fileCount fileCount
|
||||
*/
|
||||
public void setFileCount(Integer fileCount) {
|
||||
this.fileCount = fileCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Set;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.queries.TermsFilter;
|
||||
@@ -171,12 +172,17 @@ public class LuceneDao {
|
||||
TermsFilter userFilter = new TermsFilter(terms);
|
||||
|
||||
// Search
|
||||
IndexSearcher searcher = new IndexSearcher(AppContext.getInstance().getIndexingService().getDirectoryReader());
|
||||
DirectoryReader directoryReader = AppContext.getInstance().getIndexingService().getDirectoryReader();
|
||||
Set<String> documentIdList = new HashSet<String>();
|
||||
if (directoryReader == null) {
|
||||
// The directory reader is not yet initialized (probably because there is nothing indexed)
|
||||
return documentIdList;
|
||||
}
|
||||
IndexSearcher searcher = new IndexSearcher(directoryReader);
|
||||
TopDocs topDocs = searcher.search(query, userFilter, Integer.MAX_VALUE);
|
||||
ScoreDoc[] docs = topDocs.scoreDocs;
|
||||
|
||||
// Extract document IDs
|
||||
Set<String> documentIdList = new HashSet<String>();
|
||||
for (int i = 0; i < docs.length; i++) {
|
||||
org.apache.lucene.document.Document document = searcher.doc(docs[i].doc);
|
||||
String type = document.get("type");
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.sismics.docs.core.event;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Extract file content event.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ExtractFileAsyncEvent {
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sismics.docs.core.event;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
@@ -20,6 +22,11 @@ public class FileCreatedAsyncEvent {
|
||||
*/
|
||||
private Document document;
|
||||
|
||||
/**
|
||||
* Unencrypted input stream containing the file.
|
||||
*/
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* Getter of file.
|
||||
*
|
||||
@@ -56,6 +63,24 @@ public class FileCreatedAsyncEvent {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of inputStream.
|
||||
*
|
||||
* @return the inputStream
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter de inputStream.
|
||||
*
|
||||
* @param inputStream inputStream
|
||||
*/
|
||||
public void setInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.sismics.docs.core.listener.async;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.event.ExtractFileAsyncEvent;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
|
||||
/**
|
||||
* Listener on extract content from all files.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ExtractFileAsyncListener {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(ExtractFileAsyncListener.class);
|
||||
|
||||
/**
|
||||
* Extract content from all files.
|
||||
*
|
||||
* @param extractFileAsyncEvent Extract file content event
|
||||
* @throws Exception
|
||||
*/
|
||||
@Subscribe
|
||||
public void on(final ExtractFileAsyncEvent extractFileAsyncEvent) throws Exception {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Extract file content event: " + extractFileAsyncEvent.toString());
|
||||
}
|
||||
|
||||
TransactionUtil.handle(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
for (File file : fileList) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Document document = documentDao.getById(file.getDocumentId());
|
||||
file.setContent(FileUtil.extractContent(document, file));
|
||||
TransactionUtil.commit();
|
||||
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class FileCreatedAsyncListener {
|
||||
// OCR the file
|
||||
final File file = fileCreatedAsyncEvent.getFile();
|
||||
long startTime = System.currentTimeMillis();
|
||||
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getDocument(), file);
|
||||
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getDocument(), file, fileCreatedAsyncEvent.getInputStream());
|
||||
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
|
||||
|
||||
// Store the OCR-ization result in the database
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.sismics.docs.core.listener.async.DocumentDeletedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.DocumentUpdatedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.FileCreatedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.FileDeletedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.ExtractFileAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.RebuildIndexAsyncListener;
|
||||
import com.sismics.docs.core.listener.sync.DeadEventListener;
|
||||
import com.sismics.docs.core.model.jpa.Config;
|
||||
@@ -82,7 +81,6 @@ public class AppContext {
|
||||
asyncEventBus.register(new DocumentUpdatedAsyncListener());
|
||||
asyncEventBus.register(new DocumentDeletedAsyncListener());
|
||||
asyncEventBus.register(new RebuildIndexAsyncListener());
|
||||
asyncEventBus.register(new ExtractFileAsyncListener());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,7 +50,7 @@ public class Tag {
|
||||
/**
|
||||
* Tag name.
|
||||
*/
|
||||
@Column(name = "TAG_COLOR_C", nullable = false, length = 6)
|
||||
@Column(name = "TAG_COLOR_C", nullable = false, length = 7)
|
||||
private String color;
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,6 +47,12 @@ public class User {
|
||||
@Column(name = "USE_PASSWORD_C", nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* User's private key.
|
||||
*/
|
||||
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* Email address.
|
||||
*/
|
||||
@@ -257,6 +263,22 @@ public class User {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
|
||||
@@ -128,7 +128,6 @@ public class IndexingService extends AbstractScheduledService {
|
||||
public DirectoryReader getDirectoryReader() {
|
||||
if (directoryReader == null) {
|
||||
if (!DirectoryReader.indexExists(directory)) {
|
||||
log.info("Lucene directory not yet created");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* Encryption utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
|
||||
/**
|
||||
* Salt.
|
||||
*/
|
||||
private static final String SALT = "LEpxZmm2SMu2PeKzPNrar2rhVAS6LrrgvXKeL9uyXC4vgKHg";
|
||||
|
||||
static {
|
||||
// Initialize Bouncy Castle provider
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a private key.
|
||||
*
|
||||
* @return New random private key
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static String generatePrivateKey() throws NoSuchAlgorithmException {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
return new BigInteger(176, random).toString(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an InputStream using the specified private key.
|
||||
*
|
||||
* @param is InputStream to encrypt
|
||||
* @param privateKey Private key
|
||||
* @return Encrypted stream
|
||||
* @throws Exception
|
||||
*/
|
||||
public static InputStream decryptInputStream(InputStream is, String privateKey) throws Exception {
|
||||
return new CipherInputStream(is, getCipher(privateKey, Cipher.DECRYPT_MODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an encryption cipher.
|
||||
*
|
||||
* @param privateKey Private key
|
||||
* @return Encryption cipher
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Cipher getEncryptionCipher(String privateKey) throws Exception {
|
||||
if (Strings.isNullOrEmpty(privateKey)) {
|
||||
throw new IllegalArgumentException("The private key is null or empty");
|
||||
}
|
||||
return getCipher(privateKey, Cipher.ENCRYPT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a Cipher.
|
||||
*
|
||||
* @param privateKey Private key
|
||||
* @param mode Mode (encrypt or decrypt)
|
||||
* @return Cipher
|
||||
* @throws Exception
|
||||
*/
|
||||
private static Cipher getCipher(String privateKey, int mode) throws Exception {
|
||||
PBEKeySpec keySpec = new PBEKeySpec(privateKey.toCharArray(), SALT.getBytes(), 2000, 256);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
|
||||
SecretKey desKey = skf.generateSecret(keySpec);
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
|
||||
cipher.init(mode, desKey);
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
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 java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import net.sourceforge.tess4j.Tesseract;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.util.PDFTextStripper;
|
||||
@@ -23,6 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.tess4j.Tesseract;
|
||||
import com.sismics.util.ImageUtil;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
|
||||
@@ -42,36 +46,36 @@ public class FileUtil {
|
||||
*
|
||||
* @param document Document linked to the file
|
||||
* @param file File to extract
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @return Content extract
|
||||
*/
|
||||
public static String extractContent(Document document, File file) {
|
||||
public static String extractContent(Document document, File file, InputStream inputStream) {
|
||||
String content = null;
|
||||
|
||||
if (ImageUtil.isImage(file.getMimeType())) {
|
||||
content = ocrFile(document, file);
|
||||
content = ocrFile(inputStream, document);
|
||||
} else if (file.getMimeType().equals(MimeType.APPLICATION_PDF)) {
|
||||
content = extractPdf(file);
|
||||
content = extractPdf(inputStream);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optical character recognition on a file.
|
||||
* Optical character recognition on a stream.
|
||||
*
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param document Document linked to the file
|
||||
* @param file File to OCR
|
||||
* @return Content extracted
|
||||
*/
|
||||
private static String ocrFile(Document document, File file) {
|
||||
private static String ocrFile(InputStream inputStream, Document document) {
|
||||
Tesseract instance = Tesseract.getInstance();
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
String content = null;
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(storedfile);
|
||||
image = ImageIO.read(inputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading the image " + storedfile, e);
|
||||
log.error("Error reading the image", e);
|
||||
}
|
||||
|
||||
// Upscale and grayscale the image
|
||||
@@ -85,7 +89,7 @@ public class FileUtil {
|
||||
instance.setLanguage(document.getLanguage());
|
||||
content = instance.doOCR(image);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while OCR-izing the file " + storedfile, e);
|
||||
log.error("Error while OCR-izing the image", e);
|
||||
}
|
||||
|
||||
return content;
|
||||
@@ -94,19 +98,18 @@ public class FileUtil {
|
||||
/**
|
||||
* Extract text from a PDF.
|
||||
*
|
||||
* @param file File to extract
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @return Content extracted
|
||||
*/
|
||||
private static String extractPdf(File file) {
|
||||
private static String extractPdf(InputStream inputStream) {
|
||||
String content = null;
|
||||
PDDocument pdfDocument = null;
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
try {
|
||||
PDFTextStripper stripper = new PDFTextStripper();
|
||||
pdfDocument = PDDocument.load(storedfile.getAbsolutePath(), true);
|
||||
pdfDocument = PDDocument.load(inputStream, true);
|
||||
content = stripper.getText(pdfDocument);
|
||||
} catch (IOException e) {
|
||||
log.error("Error while extracting text from the PDF " + storedfile, e);
|
||||
log.error("Error while extracting text from the PDF", e);
|
||||
} finally {
|
||||
if (pdfDocument != null) {
|
||||
try {
|
||||
@@ -123,39 +126,39 @@ public class FileUtil {
|
||||
/**
|
||||
* Save a file on the storage filesystem.
|
||||
*
|
||||
* @param is InputStream
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param file File to save
|
||||
* @throws IOException
|
||||
* @param privateKey Private key used for encryption
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void save(InputStream is, File file) throws IOException {
|
||||
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());
|
||||
Files.copy(is, path);
|
||||
Files.copy(new CipherInputStream(inputStream, cipher), path);
|
||||
|
||||
// Generate file variations
|
||||
try {
|
||||
saveVariations(file, path.toFile());
|
||||
} catch (IOException e) {
|
||||
// Don't rethrow Exception from file variations generation
|
||||
log.error("Error creating file variations", e);
|
||||
}
|
||||
inputStream.reset();
|
||||
saveVariations(file, inputStream, cipher);
|
||||
inputStream.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate file variations.
|
||||
*
|
||||
* @param file File from database
|
||||
* @param originalFile Original file
|
||||
* @throws IOException
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param cipher Cipher to use for encryption
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void saveVariations(File file, java.io.File originalFile) throws IOException {
|
||||
public static void saveVariations(File file, InputStream inputStream, Cipher cipher) throws Exception {
|
||||
BufferedImage image = null;
|
||||
if (ImageUtil.isImage(file.getMimeType())) {
|
||||
image = ImageIO.read(originalFile);
|
||||
image = ImageIO.read(inputStream);
|
||||
} else if(file.getMimeType().equals(MimeType.APPLICATION_PDF)) {
|
||||
// Generate preview from the first page of the PDF
|
||||
PDDocument pdfDocument = null;
|
||||
try {
|
||||
pdfDocument = PDDocument.load(originalFile.getAbsolutePath(), true);
|
||||
pdfDocument = PDDocument.load(inputStream, true);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<PDPage> pageList = pdfDocument.getDocumentCatalog().getAllPages();
|
||||
if (pageList.size() > 0) {
|
||||
@@ -172,8 +175,24 @@ public class FileUtil {
|
||||
BufferedImage web = Scalr.resize(image, Scalr.Method.AUTOMATIC, Scalr.Mode.AUTOMATIC, 1280, Scalr.OP_ANTIALIAS);
|
||||
BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.AUTOMATIC, Scalr.Mode.AUTOMATIC, 256, Scalr.OP_ANTIALIAS);
|
||||
image.flush();
|
||||
ImageUtil.writeJpeg(web, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_web").toFile());
|
||||
ImageUtil.writeJpeg(thumbnail, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_thumb").toFile());
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
ImageUtil.writeJpeg(thumbnail, outputStream);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
173
docs-core/src/main/java/com/sismics/tess4j/ImageHelper.java
Normal file
173
docs-core/src/main/java/com/sismics/tess4j/ImageHelper.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright @ 2008 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.image.*;
|
||||
|
||||
public class ImageHelper {
|
||||
|
||||
/**
|
||||
* Convenience method that returns a scaled instance of the provided
|
||||
* {@code BufferedImage}.
|
||||
*
|
||||
* @param image the original image to be scaled
|
||||
* @param targetWidth the desired width of the scaled instance, in pixels
|
||||
* @param targetHeight the desired height of the scaled instance, in pixels
|
||||
* @return a scaled version of the original {@code BufferedImage}
|
||||
*/
|
||||
public static BufferedImage getScaledInstance(BufferedImage image, int targetWidth, int targetHeight) {
|
||||
int type = (image.getTransparency() == Transparency.OPAQUE)
|
||||
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
||||
BufferedImage tmp = new BufferedImage(targetWidth, targetHeight, type);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g2.drawImage(image, 0, 0, targetWidth, targetHeight, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A replacement for the standard
|
||||
* <code>BufferedImage.getSubimage</code> method.
|
||||
*
|
||||
* @param image
|
||||
* @param x the X coordinate of the upper-left corner of the specified
|
||||
* rectangular region
|
||||
* @param y the Y coordinate of the upper-left corner of the specified
|
||||
* rectangular region
|
||||
* @param width the width of the specified rectangular region
|
||||
* @param height the height of the specified rectangular region
|
||||
* @return a BufferedImage that is the subimage of <code>image</code>.
|
||||
*/
|
||||
public static BufferedImage getSubImage(BufferedImage image, int x, int y, int width, int height) {
|
||||
int type = (image.getTransparency() == Transparency.OPAQUE)
|
||||
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
||||
BufferedImage tmp = new BufferedImage(width, height, type);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image.getSubimage(x, y, width, height), 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to binary or B/W image.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
*/
|
||||
public static BufferedImage convertImageToBinary(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to binary or B/W image.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
* @deprecated As of release 1.1, renamed to {@link #convertImageToBinary(BufferedImage image)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static BufferedImage convertImage2Binary(BufferedImage image) {
|
||||
return convertImageToBinary(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to gray scale.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
*/
|
||||
public static BufferedImage convertImageToGrayscale(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private static final short[] invertTable;
|
||||
|
||||
static {
|
||||
invertTable = new short[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
invertTable[i] = (short) (255 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts image color.
|
||||
*
|
||||
* @param image input image
|
||||
* @return an inverted-color image
|
||||
*/
|
||||
public static BufferedImage invertImageColor(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
|
||||
BufferedImageOp invertOp = new LookupOp(new ShortLookupTable(0, invertTable), null);
|
||||
return invertOp.filter(image, tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates an image.
|
||||
*
|
||||
* @param image the original image
|
||||
* @param angle the degree of rotation
|
||||
* @return a rotated image
|
||||
*/
|
||||
public static BufferedImage rotateImage(BufferedImage image, double angle) {
|
||||
double theta = Math.toRadians(angle);
|
||||
double sin = Math.abs(Math.sin(theta));
|
||||
double cos = Math.abs(Math.cos(theta));
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
int newW = (int) Math.floor(w * cos + h * sin);
|
||||
int newH = (int) Math.floor(h * cos + w * sin);
|
||||
|
||||
BufferedImage tmp = new BufferedImage(newW, newH, image.getType());
|
||||
Graphics2D g2d = tmp.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g2d.translate((newW - w) / 2, (newH - h) / 2);
|
||||
g2d.rotate(theta, w / 2, h / 2);
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
g2d.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an image from Clipboard.
|
||||
*
|
||||
* @return image
|
||||
*/
|
||||
public static Image getClipboardImage() {
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
try {
|
||||
return (Image) clipboard.getData(DataFlavor.imageFlavor);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
docs-core/src/main/java/com/sismics/tess4j/ImageIOHelper.java
Normal file
143
docs-core/src/main/java/com/sismics/tess4j/ImageIOHelper.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright @ 2008 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
|
||||
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi;
|
||||
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi;
|
||||
|
||||
public class ImageIOHelper {
|
||||
|
||||
final static String TIFF_FORMAT = "tiff";
|
||||
|
||||
|
||||
/**
|
||||
* Gets pixel data of an
|
||||
* <code>IIOImage</code> object.
|
||||
*
|
||||
* @param image an
|
||||
* <code>IIOImage</code> object
|
||||
* @return a byte buffer of pixel data
|
||||
* @throws Exception
|
||||
*/
|
||||
public static ByteBuffer getImageByteBuffer(IIOImage image) throws IOException {
|
||||
//Set up the writeParam
|
||||
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
|
||||
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
|
||||
|
||||
//Get tif writer and set output to file
|
||||
ImageWriter writer = new TIFFImageWriterSpi().createWriterInstance();
|
||||
|
||||
//Get the stream metadata
|
||||
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(tiffWriteParam);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
|
||||
writer.setOutput(ios);
|
||||
writer.write(streamMetadata, new IIOImage(image.getRenderedImage(), null, null), tiffWriteParam);
|
||||
writer.dispose();
|
||||
|
||||
// Read the writed image
|
||||
ios.seek(0);
|
||||
ImageReader reader = new TIFFImageReaderSpi().createReaderInstance();
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
reader.setInput(ios, true, true);
|
||||
BufferedImage bi;
|
||||
try {
|
||||
bi = reader.read(0, param);
|
||||
} finally {
|
||||
reader.dispose();
|
||||
ios.close();
|
||||
}
|
||||
|
||||
return convertImageData(bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts <code>BufferedImage</code> to <code>ByteBuffer</code>.
|
||||
*
|
||||
* @param bi Input image
|
||||
* @return pixel data
|
||||
*/
|
||||
public static ByteBuffer convertImageData(BufferedImage bi) {
|
||||
byte[] pixelData = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
|
||||
// return ByteBuffer.wrap(pixelData);
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(pixelData.length);
|
||||
buf.order(ByteOrder.nativeOrder());
|
||||
buf.put(pixelData);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads image meta data.
|
||||
*
|
||||
* @param oimage
|
||||
* @return a map of meta data
|
||||
*/
|
||||
public static Map<String, String> readImageData(IIOImage oimage) {
|
||||
Map<String, String> dict = new HashMap<String, String>();
|
||||
|
||||
IIOMetadata imageMetadata = oimage.getMetadata();
|
||||
if (imageMetadata != null) {
|
||||
IIOMetadataNode dimNode = (IIOMetadataNode) imageMetadata.getAsTree("javax_imageio_1.0");
|
||||
NodeList nodes = dimNode.getElementsByTagName("HorizontalPixelSize");
|
||||
int dpiX;
|
||||
if (nodes.getLength() > 0) {
|
||||
float dpcWidth = Float.parseFloat(nodes.item(0).getAttributes().item(0).getNodeValue());
|
||||
dpiX = (int) Math.round(25.4f / dpcWidth);
|
||||
} else {
|
||||
dpiX = Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
dict.put("dpiX", String.valueOf(dpiX));
|
||||
|
||||
nodes = dimNode.getElementsByTagName("VerticalPixelSize");
|
||||
int dpiY;
|
||||
if (nodes.getLength() > 0) {
|
||||
float dpcHeight = Float.parseFloat(nodes.item(0).getAttributes().item(0).getNodeValue());
|
||||
dpiY = (int) Math.round(25.4f / dpcHeight);
|
||||
} else {
|
||||
dpiY = Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
dict.put("dpiY", String.valueOf(dpiY));
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
686
docs-core/src/main/java/com/sismics/tess4j/TessAPI.java
Normal file
686
docs-core/src/main/java/com/sismics/tess4j/TessAPI.java
Normal file
@@ -0,0 +1,686 @@
|
||||
/**
|
||||
* Copyright @ 2012 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.ptr.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
* A Java wrapper for
|
||||
* <code>Tesseract OCR 3.02 API</code> using
|
||||
* <code>JNA Interface Mapping</code>.
|
||||
*/
|
||||
public interface TessAPI extends Library {
|
||||
|
||||
static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().startsWith("windows");
|
||||
/**
|
||||
* Native library name.
|
||||
*/
|
||||
public static final String LIB_NAME = "libtesseract302";
|
||||
public static final String LIB_NAME_NON_WIN = "tesseract";
|
||||
/**
|
||||
* An instance of the class library.
|
||||
*/
|
||||
public static final TessAPI INSTANCE = (TessAPI) Native.loadLibrary(WINDOWS ? LIB_NAME : LIB_NAME_NON_WIN, TessAPI.class);
|
||||
|
||||
/**
|
||||
* When Tesseract/Cube is initialized we can choose to instantiate/load/run
|
||||
* only the Tesseract part, only the Cube part or both along with the
|
||||
* combiner. The preference of which engine to use is stored in
|
||||
* <code>tessedit_ocr_engine_mode</code>.<br /> <br /> ATTENTION: When
|
||||
* modifying this enum, please make sure to make the appropriate changes to
|
||||
* all the enums mirroring it (e.g. OCREngine in
|
||||
* cityblock/workflow/detection/detection_storage.proto). Such enums will
|
||||
* mention the connection to OcrEngineMode in the comments.
|
||||
*/
|
||||
public static interface TessOcrEngineMode {
|
||||
|
||||
public static final int OEM_TESSERACT_ONLY = (int) 0;
|
||||
public static final int OEM_CUBE_ONLY = (int) 1;
|
||||
public static final int OEM_TESSERACT_CUBE_COMBINED = (int) 2;
|
||||
public static final int OEM_DEFAULT = (int) 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* Possible modes for page layout analysis. These *must* be kept in order of
|
||||
* decreasing amount of layout analysis to be done, except for
|
||||
* <code>OSD_ONLY</code>, so that the inequality test macros below work.
|
||||
*/
|
||||
public static interface TessPageSegMode {
|
||||
|
||||
public static final int PSM_OSD_ONLY = (int) 0;
|
||||
public static final int PSM_AUTO_OSD = (int) 1;
|
||||
public static final int PSM_AUTO_ONLY = (int) 2;
|
||||
public static final int PSM_AUTO = (int) 3;
|
||||
public static final int PSM_SINGLE_COLUMN = (int) 4;
|
||||
public static final int PSM_SINGLE_BLOCK_VERT_TEXT = (int) 5;
|
||||
public static final int PSM_SINGLE_BLOCK = (int) 6;
|
||||
public static final int PSM_SINGLE_LINE = (int) 7;
|
||||
public static final int PSM_SINGLE_WORD = (int) 8;
|
||||
public static final int PSM_CIRCLE_WORD = (int) 9;
|
||||
public static final int PSM_SINGLE_CHAR = (int) 10;
|
||||
public static final int PSM_COUNT = (int) 11;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum of the elements of the page hierarchy, used in
|
||||
* <code>ResultIterator</code> to provide functions that operate on each
|
||||
* level without having to have 5x as many functions.
|
||||
*/
|
||||
public static interface TessPageIteratorLevel {
|
||||
|
||||
public static final int RIL_BLOCK = (int) 0;
|
||||
public static final int RIL_PARA = (int) 1;
|
||||
public static final int RIL_TEXTLINE = (int) 2;
|
||||
public static final int RIL_WORD = (int) 3;
|
||||
public static final int RIL_SYMBOL = (int) 4;
|
||||
};
|
||||
|
||||
public static interface TessPolyBlockType {
|
||||
|
||||
public static final int PT_UNKNOWN = (int) 0;
|
||||
public static final int PT_FLOWING_TEXT = (int) 1;
|
||||
public static final int PT_HEADING_TEXT = (int) 2;
|
||||
public static final int PT_PULLOUT_TEXT = (int) 3;
|
||||
public static final int PT_TABLE = (int) 4;
|
||||
public static final int PT_VERTICAL_TEXT = (int) 5;
|
||||
public static final int PT_CAPTION_TEXT = (int) 6;
|
||||
public static final int PT_FLOWING_IMAGE = (int) 7;
|
||||
public static final int PT_HEADING_IMAGE = (int) 8;
|
||||
public static final int PT_PULLOUT_IMAGE = (int) 9;
|
||||
public static final int PT_HORZ_LINE = (int) 10;
|
||||
public static final int PT_VERT_LINE = (int) 11;
|
||||
public static final int PT_NOISE = (int) 12;
|
||||
public static final int PT_COUNT = (int) 13;
|
||||
};
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* +------------------+
|
||||
* | 1 Aaaa Aaaa Aaaa |
|
||||
* | Aaa aa aaa aa |
|
||||
* | aaaaaa A aa aaa. |
|
||||
* | 2 |
|
||||
* | ####### c c C |
|
||||
* | ####### c c c |
|
||||
* | < ####### c c c |
|
||||
* | < ####### c c |
|
||||
* | < ####### . c |
|
||||
* | 3 ####### c |
|
||||
* +------------------+
|
||||
* </pre>
|
||||
* Orientation Example:<br />
|
||||
* ====================<br />
|
||||
* Above is a
|
||||
* diagram of some (1) English and (2) Chinese text and a (3) photo
|
||||
* credit.<br />
|
||||
* <br />
|
||||
* Upright Latin characters are represented as A and a. '<' represents
|
||||
* a latin character rotated anti-clockwise 90 degrees. Upright
|
||||
* Chinese characters are represented C and c.<br />
|
||||
* <br />
|
||||
* NOTA BENE: enum values here should match goodoc.proto<br />
|
||||
* <br />
|
||||
* If you orient your head so that "up" aligns with Orientation, then
|
||||
* the characters will appear "right side up" and readable.<br />
|
||||
* <br />
|
||||
* In the example above, both the
|
||||
* English and Chinese paragraphs are oriented so their "up" is the top of
|
||||
* the page (page up). The photo credit is read with one's head turned
|
||||
* leftward ("up" is to page left).<br />
|
||||
* <br /> The values of this enum
|
||||
* match the convention of Tesseract's osdetect.h
|
||||
*/
|
||||
public static interface TessOrientation {
|
||||
|
||||
public static final int ORIENTATION_PAGE_UP = (int) 0;
|
||||
public static final int ORIENTATION_PAGE_RIGHT = (int) 1;
|
||||
public static final int ORIENTATION_PAGE_DOWN = (int) 2;
|
||||
public static final int ORIENTATION_PAGE_LEFT = (int) 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* The grapheme clusters within a line of text are laid out logically in
|
||||
* this direction, judged when looking at the text line rotated so that its
|
||||
* Orientation is "page up".<br /> <br /> For English text, the writing
|
||||
* direction is left-to-right. For the Chinese text in the above example,
|
||||
* the writing direction is top-to-bottom.
|
||||
*/
|
||||
public static interface TessWritingDirection {
|
||||
|
||||
public static final int WRITING_DIRECTION_LEFT_TO_RIGHT = (int) 0;
|
||||
public static final int WRITING_DIRECTION_RIGHT_TO_LEFT = (int) 1;
|
||||
public static final int WRITING_DIRECTION_TOP_TO_BOTTOM = (int) 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* The text lines are read in the given sequence.<br /> <br /> In English,
|
||||
* the order is top-to-bottom. In Chinese, vertical text lines are read
|
||||
* right-to-left. Mongolian is written in vertical columns top to bottom
|
||||
* like Chinese, but the lines order left-to right.<br /> <br /> Note that
|
||||
* only some combinations make sense. For example,
|
||||
* <code>WRITING_DIRECTION_LEFT_TO_RIGHT</code> implies
|
||||
* <code>TEXTLINE_ORDER_TOP_TO_BOTTOM</code>.
|
||||
*/
|
||||
public static interface TessTextlineOrder {
|
||||
|
||||
public static final int TEXTLINE_ORDER_LEFT_TO_RIGHT = (int) 0;
|
||||
public static final int TEXTLINE_ORDER_RIGHT_TO_LEFT = (int) 1;
|
||||
public static final int TEXTLINE_ORDER_TOP_TO_BOTTOM = (int) 2;
|
||||
};
|
||||
public static final int TRUE = (int) 1;
|
||||
public static final int FALSE = (int) 0;
|
||||
|
||||
/**
|
||||
* Returns the version identifier.
|
||||
*/
|
||||
String TessVersion();
|
||||
|
||||
void TessDeleteText(Pointer text);
|
||||
|
||||
void TessDeleteTextArray(PointerByReference arr);
|
||||
|
||||
void TessDeleteIntArray(IntBuffer arr);
|
||||
|
||||
/**
|
||||
* Creates an instance of the base class for all Tesseract APIs.
|
||||
*/
|
||||
TessAPI.TessBaseAPI TessBaseAPICreate();
|
||||
|
||||
/**
|
||||
* Disposes the TesseractAPI instance.
|
||||
*/
|
||||
void TessBaseAPIDelete(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Set the name of the input file. Needed only for training and reading a
|
||||
* UNLV zone file.
|
||||
*/
|
||||
void TessBaseAPISetInputName(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Set the name of the bonus output files. Needed only for debugging.
|
||||
*/
|
||||
void TessBaseAPISetOutputName(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Set the value of an internal "parameter." Supply the name of the
|
||||
* parameter and the value as a string, just as you would in a config file.
|
||||
* Returns false if the name lookup failed. E.g.,
|
||||
* <code>SetVariable("tessedit_char_blacklist", "xyz");</code> to ignore x,
|
||||
* y and z. Or
|
||||
* <code>SetVariable("classify_bln_numeric_mode", "1");</code> to set
|
||||
* numeric-only mode.
|
||||
* <code>SetVariable</code> may be used before
|
||||
* <code>Init</code>, but settings will revert to defaults on
|
||||
* <code>End()</code>.<br /> <br /> Note: Must be called after
|
||||
* <code>Init()</code>. Only works for non-init variables (init variables
|
||||
* should be passed to
|
||||
* <code>Init()</code>).
|
||||
*/
|
||||
int TessBaseAPISetVariable(TessAPI.TessBaseAPI handle, String name, String value);
|
||||
|
||||
/**
|
||||
* Returns true (1) if the parameter was found among Tesseract parameters.
|
||||
* Fills in value with the value of the parameter.
|
||||
*/
|
||||
int TessBaseAPIGetIntVariable(TessAPI.TessBaseAPI handle, String name, IntBuffer value);
|
||||
|
||||
int TessBaseAPIGetBoolVariable(TessAPI.TessBaseAPI handle, String name, IntBuffer value);
|
||||
|
||||
int TessBaseAPIGetDoubleVariable(TessAPI.TessBaseAPI handle, String name, DoubleBuffer value);
|
||||
|
||||
String TessBaseAPIGetStringVariable(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Print Tesseract parameters to the given file.<br /> <br /> Note: Must not
|
||||
* be the first method called after instance create.
|
||||
*/
|
||||
void TessBaseAPIPrintVariablesToFile(TessAPI.TessBaseAPI handle, String filename);
|
||||
|
||||
/**
|
||||
* Instances are now mostly thread-safe and totally independent, but some
|
||||
* global parameters remain. Basically it is safe to use multiple
|
||||
* TessBaseAPIs in different threads in parallel, UNLESS: you use
|
||||
* <code>SetVariable</code> on some of the Params in classify and textord.
|
||||
* If you do, then the effect will be to change it for all your
|
||||
* instances.<br /> <br /> Start tesseract. Returns zero on success and -1
|
||||
* on failure. NOTE that the only members that may be called before Init are
|
||||
* those listed above here in the class definition.<br /> <br /> The
|
||||
* <code>datapath</code> must be the name of the parent directory of
|
||||
* tessdata and must end in / . Any name after the last / will be stripped.
|
||||
* The language is (usually) an
|
||||
* <code>ISO 639-3</code> string or
|
||||
* <code>NULL</code> will default to eng. It is entirely safe (and
|
||||
* eventually will be efficient too) to call Init multiple times on the same
|
||||
* instance to change language, or just to reset the classifier. The
|
||||
* language may be a string of the form [~]<lang>[+[~]<lang>]* indicating
|
||||
* that multiple languages are to be loaded. E.g., hin+eng will load Hindi
|
||||
* and English. Languages may specify internally that they want to be loaded
|
||||
* with one or more other languages, so the ~ sign is available to override
|
||||
* that. E.g., if hin were set to load eng by default, then hin+~eng would
|
||||
* force loading only hin. The number of loaded languages is limited only by
|
||||
* memory, with the caveat that loading additional languages will impact
|
||||
* both speed and accuracy, as there is more work to do to decide on the
|
||||
* applicable language, and there is more chance of hallucinating incorrect
|
||||
* words. WARNING: On changing languages, all Tesseract parameters are reset
|
||||
* back to their default values. (Which may vary between languages.) If you
|
||||
* have a rare need to set a Variable that controls initialization for a
|
||||
* second call to
|
||||
* <code>Init</code> you should explicitly call
|
||||
* <code>End()</code> and then use
|
||||
* <code>SetVariable</code> before
|
||||
* <code>Init</code>. This is only a very rare use case, since there are
|
||||
* very few uses that require any parameters to be set before
|
||||
* <code>Init</code>.<br /> <br /> If
|
||||
* <code>set_only_non_debug_params</code> is true, only params that do not
|
||||
* contain "debug" in the name will be set.
|
||||
*/
|
||||
int TessBaseAPIInit1(TessAPI.TessBaseAPI handle, String datapath, String language, int oem, PointerByReference configs, int configs_size);
|
||||
|
||||
int TessBaseAPIInit2(TessAPI.TessBaseAPI handle, String datapath, String language, int oem);
|
||||
|
||||
int TessBaseAPIInit3(TessAPI.TessBaseAPI handle, String datapath, String language);
|
||||
|
||||
/**
|
||||
* Returns the languages string used in the last valid initialization. If
|
||||
* the last initialization specified "deu+hin" then that will be returned.
|
||||
* If hin loaded eng automatically as well, then that will not be included
|
||||
* in this list. To find the languages actually loaded, use
|
||||
* <code>GetLoadedLanguagesAsVector</code>. The returned string should NOT
|
||||
* be deleted.
|
||||
*/
|
||||
String TessBaseAPIGetInitLanguagesAsString(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the loaded languages in the vector of STRINGs. Includes all
|
||||
* languages loaded by the last
|
||||
* <code>Init</code>, including those loaded as dependencies of other loaded
|
||||
* languages.
|
||||
*/
|
||||
PointerByReference TessBaseAPIGetLoadedLanguagesAsVector(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the available languages in the vector of STRINGs.
|
||||
*/
|
||||
PointerByReference TessBaseAPIGetAvailableLanguagesAsVector(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Init only the lang model component of Tesseract. The only functions that
|
||||
* work after this init are
|
||||
* <code>SetVariable</code> and
|
||||
* <code>IsValidWord</code>. WARNING: temporary! This function will be
|
||||
* removed from here and placed in a separate API at some future time.
|
||||
*/
|
||||
int TessBaseAPIInitLangMod(TessAPI.TessBaseAPI handle, String datapath, String language);
|
||||
|
||||
/**
|
||||
* Init only for page layout analysis. Use only for calls to
|
||||
* <code>SetImage</code> and
|
||||
* <code>AnalysePage</code>. Calls that attempt recognition will generate an
|
||||
* error.
|
||||
*/
|
||||
void TessBaseAPIInitForAnalysePage(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Read a "config" file containing a set of param, value pairs. Searches the
|
||||
* standard places:
|
||||
* <code>tessdata/configs</code>,
|
||||
* <code>tessdata/tessconfigs</code> and also accepts a relative or absolute
|
||||
* path name. Note: only non-init params will be set (init params are set by
|
||||
* <code>Init()</code>).
|
||||
*/
|
||||
void TessBaseAPIReadConfigFile(TessAPI.TessBaseAPI handle, String filename, int init_only);
|
||||
|
||||
/**
|
||||
* Set the current page segmentation mode. Defaults to PSM_SINGLE_BLOCK. The
|
||||
* mode is stored as an IntParam so it can also be modified by
|
||||
* <code>ReadConfigFile</code> or
|
||||
* <code>SetVariable("tessedit_pageseg_mode", mode as string)</code>.
|
||||
*/
|
||||
void TessBaseAPISetPageSegMode(TessAPI.TessBaseAPI handle, int mode);
|
||||
|
||||
/**
|
||||
* Return the current page segmentation mode.
|
||||
*/
|
||||
int TessBaseAPIGetPageSegMode(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognize a rectangle from an image and return the result as a string.
|
||||
* May be called many times for a single
|
||||
* <code>Init</code>. Currently has no error checking. Greyscale of 8 and
|
||||
* color of 24 or 32 bits per pixel may be given. Palette color images will
|
||||
* not work properly and must be converted to 24 bit. Binary images of 1 bit
|
||||
* per pixel may also be given but they must be byte packed with the MSB of
|
||||
* the first byte being the first pixel, and a 1 represents WHITE. For
|
||||
* binary images set bytes_per_pixel=0. The recognized text is returned as a
|
||||
* char* which is coded as UTF8 and must be freed with the delete []
|
||||
* operator.<br /> <br /> Note that
|
||||
* <code>TesseractRect</code> is the simplified convenience interface. For
|
||||
* advanced uses, use
|
||||
* <code>SetImage</code>, (optionally)
|
||||
* <code>SetRectangle</code>,
|
||||
* <code>Recognize</code>, and one or more of the
|
||||
* <code>Get*Text</code> functions below.
|
||||
*/
|
||||
Pointer TessBaseAPIRect(TessAPI.TessBaseAPI handle, ByteBuffer imagedata, int bytes_per_pixel, int bytes_per_line, int left, int top, int width, int height);
|
||||
|
||||
/**
|
||||
* Call between pages or documents etc to free up memory and forget adaptive
|
||||
* data.
|
||||
*/
|
||||
void TessBaseAPIClearAdaptiveClassifier(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Provide an image for Tesseract to recognize. Format is as TesseractRect
|
||||
* above. Does not copy the image buffer, or take ownership. The source
|
||||
* image may be destroyed after Recognize is called, either explicitly or
|
||||
* implicitly via one of the
|
||||
* <code>Get*Text</code> functions.
|
||||
* <code>SetImage</code> clears all recognition results, and sets the
|
||||
* rectangle to the full image, so it may be followed immediately by a
|
||||
* <code>GetUTF8Text</code>, and it will automatically perform recognition.
|
||||
*/
|
||||
void TessBaseAPISetImage(TessAPI.TessBaseAPI handle, ByteBuffer imagedata, int width, int height, int bytes_per_pixel, int bytes_per_line);
|
||||
|
||||
/**
|
||||
* Set the resolution of the source image in pixels per inch so font size
|
||||
* information can be calculated in results. Call this after SetImage().
|
||||
*/
|
||||
void TessBaseAPISetSourceResolution(TessAPI.TessBaseAPI handle, int ppi);
|
||||
|
||||
/**
|
||||
* Restrict recognition to a sub-rectangle of the image. Call after
|
||||
* <code>SetImage</code>. Each
|
||||
* <code>SetRectangle</code> clears the recognition results so multiple
|
||||
* rectangles can be recognized with the same image.
|
||||
*/
|
||||
void TessBaseAPISetRectangle(TessAPI.TessBaseAPI handle, int left, int top, int width, int height);
|
||||
|
||||
/** Scale factor from original image. */
|
||||
int TessBaseAPIGetThresholdedImageScaleFactor(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/** Dump the internal binary image to a PGM file. */
|
||||
void TessBaseAPIDumpPGM(TessAPI.TessBaseAPI handle, String filename);
|
||||
|
||||
/**
|
||||
* Runs page layout analysis in the mode set by SetPageSegMode. May
|
||||
* optionally be called prior to Recognize to get access to just the page
|
||||
* layout results. Returns an iterator to the results. Returns NULL on
|
||||
* error. The returned iterator must be deleted after use. WARNING! This
|
||||
* class points to data held within the TessBaseAPI class, and therefore can
|
||||
* only be used while the TessBaseAPI class still exists and has not been
|
||||
* subjected to a call of
|
||||
* <code>Init</code>,
|
||||
* <code>SetImage</code>,
|
||||
* <code>Recognize</code>,
|
||||
* <code>Clear</code>,
|
||||
* <code>End</code>, DetectOS, or anything else that changes the internal
|
||||
* PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessPageIterator TessBaseAPIAnalyseLayout(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognize the image from SetAndThresholdImage, generating Tesseract
|
||||
* internal structures. Returns 0 on success. Optional. The
|
||||
* <code>Get*Text</code> functions below will call
|
||||
* <code>Recognize</code> if needed. After Recognize, the output is kept
|
||||
* internally until the next
|
||||
* <code>SetImage</code>.
|
||||
*/
|
||||
int TessBaseAPIRecognize(TessAPI.TessBaseAPI handle, TessAPI.ETEXT_DESC monitor);
|
||||
|
||||
/**
|
||||
* Variant on Recognize used for testing chopper.
|
||||
*/
|
||||
int TessBaseAPIRecognizeForChopTest(TessAPI.TessBaseAPI handle, TessAPI.ETEXT_DESC monitor);
|
||||
|
||||
/**
|
||||
* Get a reading-order iterator to the results of LayoutAnalysis and/or
|
||||
* Recognize. The returned iterator must be deleted after use. WARNING! This
|
||||
* class points to data held within the TessBaseAPI class, and therefore can
|
||||
* only be used while the TessBaseAPI class still exists and has not been
|
||||
* subjected to a call of
|
||||
* <code>Init</code>,
|
||||
* <code>SetImage</code>,
|
||||
* <code>Recognize</code>,
|
||||
* <code>Clear</code>,
|
||||
* <code>End</code>, DetectOS, or anything else that changes the internal
|
||||
* PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessResultIterator TessBaseAPIGetIterator(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Get a mutable iterator to the results of LayoutAnalysis and/or Recognize.
|
||||
* The returned iterator must be deleted after use.
|
||||
* WARNING! This class points to data held within the TessBaseAPI class, and
|
||||
* therefore can only be used while the TessBaseAPI class still exists and
|
||||
* has not been subjected to a call of Init, SetImage, Recognize, Clear, End
|
||||
* DetectOS, or anything else that changes the internal PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessMutableIterator TessBaseAPIGetMutableIterator(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognizes all the pages in the named file, as a multi-page tiff or list
|
||||
* of filenames, or single image, and gets the appropriate kind of text
|
||||
* according to parameters:
|
||||
* <code>tessedit_create_boxfile</code>,
|
||||
* <code>tessedit_make_boxes_from_boxes</code>,
|
||||
* <code>tessedit_write_unlv</code>,
|
||||
* <code>tessedit_create_hocr</code>. Calls ProcessPage on each page in the
|
||||
* input file, which may be a multi-page tiff, single-page other file
|
||||
* format, or a plain text list of images to read. If tessedit_page_number
|
||||
* is non-negative, processing begins at that page of a multi-page tiff
|
||||
* file, or filelist. The text is returned in text_out. Returns false on
|
||||
* error. If non-zero timeout_millisec terminates processing after the
|
||||
* timeout on a single page. If non-NULL and non-empty, and some page fails
|
||||
* for some reason, the page is reprocessed with the retry_config config
|
||||
* file. Useful for interactively debugging a bad page.
|
||||
*/
|
||||
Pointer TessBaseAPIProcessPages(TessAPI.TessBaseAPI handle, String filename, String retry_config, int timeout_millisec);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded as UTF-8 and
|
||||
* must be freed with the delete [] operator.
|
||||
*/
|
||||
Pointer TessBaseAPIGetUTF8Text(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Make a HTML-formatted string with hOCR markup from the internal data
|
||||
* structures. page_number is 0-based but will appear in the output as
|
||||
* 1-based.
|
||||
*/
|
||||
Pointer TessBaseAPIGetHOCRText(TessAPI.TessBaseAPI handle, int page_number);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded in the same
|
||||
* format as a box file used in training. Returned string must be freed with
|
||||
* the delete [] operator. Constructs coordinates in the original image -
|
||||
* not just the rectangle. page_number is a 0-based page index that will
|
||||
* appear in the box file.
|
||||
*/
|
||||
Pointer TessBaseAPIGetBoxText(TessAPI.TessBaseAPI handle, int page_number);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded as UNLV format
|
||||
* Latin-1 with specific reject and suspect codes and must be freed with the
|
||||
* delete [] operator.
|
||||
*/
|
||||
Pointer TessBaseAPIGetUNLVText(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the (average) confidence value between 0 and 100.
|
||||
*/
|
||||
int TessBaseAPIMeanTextConf(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns all word confidences (between 0 and 100) in an array, terminated
|
||||
* by -1. The calling function must delete [] after use. The number of
|
||||
* confidences should correspond to the number of space-delimited words in
|
||||
* GetUTF8Text.
|
||||
*/
|
||||
IntByReference TessBaseAPIAllWordConfidences(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Applies the given word to the adaptive classifier if possible. The word
|
||||
* must be SPACE-DELIMITED UTF-8 - l i k e t h i s , so it can tell the
|
||||
* boundaries of the graphemes. Assumes that SetImage/SetRectangle have been
|
||||
* used to set the image to the given word. The mode arg should be
|
||||
* PSM_SINGLE_WORD or PSM_CIRCLE_WORD, as that will be used to control
|
||||
* layout analysis. The currently set PageSegMode is preserved. Returns
|
||||
* false if adaption was not possible for some reason.
|
||||
*/
|
||||
int TessBaseAPIAdaptToWordStr(TessAPI.TessBaseAPI handle, int mode, String wordstr);
|
||||
|
||||
/**
|
||||
* Free up recognition results and any stored image data, without actually
|
||||
* freeing any recognition data that would be time-consuming to reload.
|
||||
* Afterwards, you must call
|
||||
* <code>SetImage</code> or
|
||||
* <code>TesseractRect</code> before doing any
|
||||
* <code>Recognize</code> or
|
||||
* <code>Get*</code> operation.
|
||||
*/
|
||||
void TessBaseAPIClear(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Close down tesseract and free up all memory.
|
||||
* <code>End()</code> is equivalent to destructing and reconstructing your
|
||||
* TessBaseAPI. Once
|
||||
* <code>End()</code> has been used, none of the other API functions may be
|
||||
* used other than
|
||||
* <code>Init</code> and anything declared above it in the class definition.
|
||||
*/
|
||||
void TessBaseAPIEnd(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Check whether a word is valid according to Tesseract's language model.
|
||||
*
|
||||
* @return 0 if the word is invalid, non-zero if valid. @warning temporary!
|
||||
* This function will be removed from here and placed in a separate API at
|
||||
* some future time.
|
||||
*/
|
||||
int TessBaseAPIIsValidWord(TessAPI.TessBaseAPI handle, String word);
|
||||
|
||||
int TessBaseAPIGetTextDirection(TessAPI.TessBaseAPI handle, IntBuffer out_offset, FloatBuffer out_slope);
|
||||
|
||||
/**
|
||||
* This method returns the string form of the specified unichar.
|
||||
*/
|
||||
String TessBaseAPIGetUnichar(TessAPI.TessBaseAPI handle, int unichar_id);
|
||||
|
||||
/* Page iterator */
|
||||
void TessPageIteratorDelete(TessAPI.TessPageIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessPageIteratorCopy(TessAPI.TessPageIterator handle);
|
||||
|
||||
void TessPageIteratorBegin(TessAPI.TessPageIterator handle);
|
||||
|
||||
int TessPageIteratorNext(TessAPI.TessPageIterator handle, int level);
|
||||
|
||||
int TessPageIteratorIsAtBeginningOf(TessAPI.TessPageIterator handle, int level);
|
||||
|
||||
int TessPageIteratorIsAtFinalElement(TessAPI.TessPageIterator handle, int level, int element);
|
||||
|
||||
int TessPageIteratorBoundingBox(TessAPI.TessPageIterator handle, int level, IntBuffer left, IntBuffer top, IntBuffer right, IntBuffer bottom);
|
||||
|
||||
int TessPageIteratorBlockType(TessAPI.TessPageIterator handle);
|
||||
|
||||
int TessPageIteratorBaseline(TessAPI.TessPageIterator handle, int level, IntBuffer x1, IntBuffer y1, IntBuffer x2, IntBuffer y2);
|
||||
|
||||
void TessPageIteratorOrientation(TessAPI.TessPageIterator handle, IntBuffer orientation, IntBuffer writing_direction, IntBuffer textline_order, FloatBuffer deskew_angle);
|
||||
|
||||
/* Result iterator */
|
||||
void TessResultIteratorDelete(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessResultIterator TessResultIteratorCopy(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessResultIteratorGetPageIterator(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessResultIteratorGetPageIteratorConst(TessAPI.TessResultIterator handle);
|
||||
|
||||
Pointer TessResultIteratorGetUTF8Text(TessAPI.TessResultIterator handle, int level);
|
||||
|
||||
float TessResultIteratorConfidence(TessAPI.TessResultIterator handle, int level);
|
||||
|
||||
String TessResultIteratorWordFontAttributes(TessAPI.TessResultIterator handle, IntBuffer is_bold, IntBuffer is_italic, IntBuffer is_underlined, IntBuffer is_monospace, IntBuffer is_serif, IntBuffer is_smallcaps, IntBuffer pointsize, IntBuffer font_id);
|
||||
|
||||
int TessResultIteratorWordIsFromDictionary(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorWordIsNumeric(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsSuperscript(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsSubscript(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsDropcap(TessAPI.TessResultIterator handle);
|
||||
|
||||
public static class TessBaseAPI extends PointerType {
|
||||
|
||||
public TessBaseAPI(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessBaseAPI() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class ETEXT_DESC extends PointerType {
|
||||
|
||||
public ETEXT_DESC(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public ETEXT_DESC() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessPageIterator extends PointerType {
|
||||
|
||||
public TessPageIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessPageIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessMutableIterator extends PointerType {
|
||||
|
||||
public TessMutableIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessMutableIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessResultIterator extends PointerType {
|
||||
|
||||
public TessResultIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessResultIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
}
|
||||
256
docs-core/src/main/java/com/sismics/tess4j/Tesseract.java
Normal file
256
docs-core/src/main/java/com/sismics/tess4j/Tesseract.java
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright @ 2012 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* An object layer on top of
|
||||
* <code>TessAPI</code>, provides character recognition support for common image
|
||||
* formats, and multi-page TIFF images beyond the uncompressed, binary TIFF
|
||||
* format supported by Tesseract OCR engine. The extended capabilities are
|
||||
* provided by the
|
||||
* <code>Java Advanced Imaging Image I/O Tools</code>. <br /><br /> Support for
|
||||
* PDF documents is available through
|
||||
* <code>Ghost4J</code>, a
|
||||
* <code>JNA</code> wrapper for
|
||||
* <code>GPL Ghostscript</code>, which should be installed and included in
|
||||
* system path. <br /><br /> Any program that uses the library will need to
|
||||
* ensure that the required libraries (the
|
||||
* <code>.jar</code> files for
|
||||
* <code>jna</code>,
|
||||
* <code>jai-imageio</code>, and
|
||||
* <code>ghost4j</code>) are in its compile and run-time
|
||||
* <code>classpath</code>.
|
||||
*/
|
||||
public class Tesseract {
|
||||
|
||||
private static Tesseract instance;
|
||||
private final static Rectangle EMPTY_RECTANGLE = new Rectangle();
|
||||
private String language = "eng";
|
||||
private String datapath = "tessdata";
|
||||
private int psm = TessAPI.TessPageSegMode.PSM_AUTO;
|
||||
private boolean hocr;
|
||||
private int pageNum;
|
||||
private int ocrEngineMode = TessAPI.TessOcrEngineMode.OEM_DEFAULT;
|
||||
private Properties prop = new Properties();
|
||||
public final static String htmlBeginTag =
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
|
||||
+ " \"http://www.w3.org/TR/html4/loose.dtd\">\n"
|
||||
+ "<html>\n<head>\n<title></title>\n"
|
||||
+ "<meta http-equiv=\"Content-Type\" content=\"text/html;"
|
||||
+ "charset=utf-8\" />\n<meta name='ocr-system' content='tesseract'/>\n"
|
||||
+ "</head>\n<body>\n";
|
||||
public final static String htmlEndTag = "</body>\n</html>\n";
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private Tesseract() {
|
||||
System.setProperty("jna.encoding", "UTF8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of the class library.
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
public static synchronized Tesseract getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Tesseract();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tessdata path.
|
||||
*
|
||||
* @param datapath the tessdata path to set
|
||||
*/
|
||||
public void setDatapath(String datapath) {
|
||||
this.datapath = datapath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets language for OCR.
|
||||
*
|
||||
* @param language the language code, which follows ISO 639-3 standard.
|
||||
*/
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets OCR engine mode.
|
||||
*
|
||||
* @param ocrEngineMode the OcrEngineMode to set
|
||||
*/
|
||||
public void setOcrEngineMode(int ocrEngineMode) {
|
||||
this.ocrEngineMode = ocrEngineMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets page segmentation mode.
|
||||
*
|
||||
* @param mode the page segmentation mode to set
|
||||
*/
|
||||
public void setPageSegMode(int mode) {
|
||||
this.psm = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables hocr output.
|
||||
*
|
||||
* @param hocr to enable or disable hocr output
|
||||
*/
|
||||
public void setHocr(boolean hocr) {
|
||||
this.hocr = hocr;
|
||||
prop.setProperty("tessedit_create_hocr", hocr ? "1" : "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of Tesseract's internal parameter.
|
||||
*
|
||||
* @param key variable name, e.g.,
|
||||
* <code>tessedit_create_hocr</code>,
|
||||
* <code>tessedit_char_whitelist</code>, etc.
|
||||
* @param value value for corresponding variable, e.g., "1", "0",
|
||||
* "0123456789", etc.
|
||||
*/
|
||||
public void setTessVariable(String key, String value) {
|
||||
prop.setProperty(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param bi a buffered image
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(BufferedImage bi) throws TesseractException {
|
||||
return doOCR(bi, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param bi a buffered image
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(BufferedImage bi, Rectangle rect) throws TesseractException {
|
||||
IIOImage oimage = new IIOImage(bi, null, null);
|
||||
List<IIOImage> imageList = new ArrayList<IIOImage>();
|
||||
imageList.add(oimage);
|
||||
return doOCR(imageList, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param imageList a list of
|
||||
* <code>IIOImage</code> objects
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(List<IIOImage> imageList, Rectangle rect) throws TesseractException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
pageNum = 0;
|
||||
|
||||
for (IIOImage oimage : imageList) {
|
||||
pageNum++;
|
||||
try {
|
||||
ByteBuffer buf = ImageIOHelper.getImageByteBuffer(oimage);
|
||||
RenderedImage ri = oimage.getRenderedImage();
|
||||
String pageText = doOCR(ri.getWidth(), ri.getHeight(), buf, rect, ri.getColorModel().getPixelSize());
|
||||
sb.append(pageText);
|
||||
} catch (IOException ioe) {
|
||||
//skip the problematic image
|
||||
System.err.println(ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (hocr) {
|
||||
sb.insert(0, htmlBeginTag).append(htmlEndTag);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation. Use
|
||||
* <code>SetImage</code>, (optionally)
|
||||
* <code>SetRectangle</code>, and one or more of the
|
||||
* <code>Get*Text</code> functions.
|
||||
*
|
||||
* @param xsize width of image
|
||||
* @param ysize height of image
|
||||
* @param buf pixel data
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @param bpp bits per pixel, represents the bit depth of the image, with 1
|
||||
* for binary bitmap, 8 for gray, and 24 for color RGB.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(int xsize, int ysize, ByteBuffer buf, Rectangle rect, int bpp) throws TesseractException {
|
||||
TessAPI api = TessAPI.INSTANCE;
|
||||
TessAPI.TessBaseAPI handle = api.TessBaseAPICreate();
|
||||
api.TessBaseAPIInit2(handle, datapath, language, ocrEngineMode);
|
||||
api.TessBaseAPISetPageSegMode(handle, psm);
|
||||
|
||||
Enumeration<?> em = prop.propertyNames();
|
||||
while (em.hasMoreElements()) {
|
||||
String key = (String) em.nextElement();
|
||||
api.TessBaseAPISetVariable(handle, key, prop.getProperty(key));
|
||||
}
|
||||
|
||||
int bytespp = bpp / 8;
|
||||
int bytespl = (int) Math.ceil(xsize * bpp / 8.0);
|
||||
api.TessBaseAPISetImage(handle, buf, xsize, ysize, bytespp, bytespl);
|
||||
|
||||
if (rect != null && !rect.equals(EMPTY_RECTANGLE)) {
|
||||
api.TessBaseAPISetRectangle(handle, rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
Pointer utf8Text = hocr ? api.TessBaseAPIGetHOCRText(handle, pageNum - 1) : api.TessBaseAPIGetUTF8Text(handle);
|
||||
String str = utf8Text.getString(0);
|
||||
api.TessDeleteText(utf8Text);
|
||||
api.TessBaseAPIDelete(handle);
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright @ 2010 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.sismics.tess4j;
|
||||
|
||||
public class TesseractException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TesseractException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TesseractException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TesseractException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public TesseractException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.sismics.util;
|
||||
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.FileImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import com.sismics.util.mime.MimeType;
|
||||
|
||||
/**
|
||||
* Image processing utilities.
|
||||
@@ -23,26 +24,26 @@ public class ImageUtil {
|
||||
* Write a high quality JPEG.
|
||||
*
|
||||
* @param image
|
||||
* @param file
|
||||
* @param outputStream Output stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void writeJpeg(BufferedImage image, File file) throws IOException {
|
||||
public static void writeJpeg(BufferedImage image, OutputStream outputStream) throws IOException {
|
||||
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
|
||||
ImageWriter writer = null;
|
||||
FileImageOutputStream output = null;
|
||||
ImageOutputStream imageOutputStream = null;
|
||||
try {
|
||||
writer = (ImageWriter) iter.next();
|
||||
ImageWriteParam iwp = writer.getDefaultWriteParam();
|
||||
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
iwp.setCompressionQuality(1.f);
|
||||
output = new FileImageOutputStream(file);
|
||||
writer.setOutput(output);
|
||||
imageOutputStream = ImageIO.createImageOutputStream(outputStream);
|
||||
writer.setOutput(imageOutputStream);
|
||||
IIOImage iioImage = new IIOImage(image, null, null);
|
||||
writer.write(null, iioImage, iwp);
|
||||
} finally {
|
||||
if (output != null) {
|
||||
if (imageOutputStream != null) {
|
||||
try {
|
||||
output.close();
|
||||
imageOutputStream.close();
|
||||
} catch (Exception inner) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@@ -54,28 +54,33 @@ public class ResourceUtil {
|
||||
// Extract the JAR path
|
||||
String jarPath = dirUrl.getPath().substring(5, dirUrl.getPath().indexOf("!"));
|
||||
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
|
||||
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
Set<String> fileSet = new HashSet<String>();
|
||||
while (entries.hasMoreElements()) {
|
||||
// Filter according to the path
|
||||
String entryName = entries.nextElement().getName();
|
||||
if (!entryName.startsWith(path)) {
|
||||
continue;
|
||||
}
|
||||
String name = entryName.substring(path.length());
|
||||
if (!"".equals(name)) {
|
||||
// If it is a subdirectory, just return the directory name
|
||||
int checkSubdir = name.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
name = name.substring(0, checkSubdir);
|
||||
}
|
||||
|
||||
if (filter == null || filter.accept(null, name)) {
|
||||
fileSet.add(name);
|
||||
try {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
// Filter according to the path
|
||||
String entryName = entries.nextElement().getName();
|
||||
if (!entryName.startsWith(path)) {
|
||||
continue;
|
||||
}
|
||||
String name = entryName.substring(path.length());
|
||||
if (!"".equals(name)) {
|
||||
// If it is a subdirectory, just return the directory name
|
||||
int checkSubdir = name.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
name = name.substring(0, checkSubdir);
|
||||
}
|
||||
|
||||
if (filter == null || filter.accept(null, name)) {
|
||||
fileSet.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
jar.close();
|
||||
}
|
||||
|
||||
return Lists.newArrayList(fileSet);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ public class MemoryAppender extends AppenderSkeleton {
|
||||
|
||||
@Override
|
||||
public synchronized void append(LoggingEvent event) {
|
||||
// TODO Don't use size()
|
||||
while (logQueue.size() > size) {
|
||||
logQueue.remove();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sismics.util.mime;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Utility to check MIME types.
|
||||
@@ -24,6 +25,18 @@ public class MimeTypeUtil {
|
||||
if (readCount <= 0) {
|
||||
throw new Exception("Cannot read input file");
|
||||
}
|
||||
|
||||
return guessMimeType(headerBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the MIME type of a file by its magic number (header).
|
||||
*
|
||||
* @param headerBytes File header (first bytes)
|
||||
* @return MIME type
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String guessMimeType(byte[] headerBytes) throws UnsupportedEncodingException {
|
||||
String header = new String(headerBytes, "US-ASCII");
|
||||
|
||||
if (header.startsWith("PK")) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
db.version=5
|
||||
db.version=6
|
||||
@@ -0,0 +1,3 @@
|
||||
alter table T_USER add column USE_PRIVATEKEY_C varchar(100) default '' not null;
|
||||
update T_USER set USE_PRIVATEKEY_C = 'AdminPk' where USE_ID_C = 'admin';
|
||||
update T_CONFIG set CFG_VALUE_C='6' where CFG_ID_C='DB_VERSION';
|
||||
@@ -21,6 +21,7 @@ public class TestJpa extends BaseTransactionalTest {
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setLocaleId("fr");
|
||||
user.setRoleId("admin");
|
||||
user.setPrivateKey("AwesomePrivateKey");
|
||||
String id = userDao.create(user);
|
||||
|
||||
TransactionUtil.commit();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
/**
|
||||
* Test of the encryption utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestEncryptUtil {
|
||||
|
||||
/**
|
||||
* Test private key.
|
||||
*/
|
||||
String pk = "OnceUponATime";
|
||||
|
||||
@Test
|
||||
public void generatePrivateKeyTest() throws Exception {
|
||||
String key = EncryptionUtil.generatePrivateKey();
|
||||
System.out.println(key);
|
||||
Assert.assertFalse(Strings.isNullOrEmpty(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptStreamTest() throws Exception {
|
||||
try {
|
||||
EncryptionUtil.getEncryptionCipher("");
|
||||
Assert.fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
}
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher(pk);
|
||||
InputStream inputStream = new CipherInputStream(this.getClass().getResourceAsStream("/file/udhr.pdf"), cipher);
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"));
|
||||
Assert.assertTrue(ByteStreams.equal(
|
||||
ByteStreams.newInputStreamSupplier(encryptedData),
|
||||
ByteStreams.newInputStreamSupplier(assertData)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptStreamTest() throws Exception {
|
||||
InputStream inputStream = EncryptionUtil.decryptInputStream(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"), pk);
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr.pdf"));
|
||||
Assert.assertTrue(ByteStreams.equal(
|
||||
ByteStreams.newInputStreamSupplier(encryptedData),
|
||||
ByteStreams.newInputStreamSupplier(assertData)));
|
||||
}
|
||||
}
|
||||
BIN
docs-core/src/test/resources/file/udhr.pdf
Normal file
BIN
docs-core/src/test/resources/file/udhr.pdf
Normal file
Binary file not shown.
BIN
docs-core/src/test/resources/file/udhr_encrypted.pdf
Normal file
BIN
docs-core/src/test/resources/file/udhr_encrypted.pdf
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
- Automatic backup system using Quartz (server)
|
||||
|
||||
Binary file not shown.
@@ -63,6 +63,7 @@
|
||||
<org.vafer.jdeb.version>1.0.1</org.vafer.jdeb.version>
|
||||
<com.samaxes.maven.minify-maven-plugin.version>1.7</com.samaxes.maven.minify-maven-plugin.version>
|
||||
<org.apache.pdfbox.pdfbox.version>1.8.2</org.apache.pdfbox.pdfbox.version>
|
||||
<org.bouncycastle.bcprov-jdk15on.version>1.49</org.bouncycastle.bcprov-jdk15on.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
@@ -443,6 +444,12 @@
|
||||
<version>${org.apache.pdfbox.pdfbox.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${org.bouncycastle.bcprov-jdk15on.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
@@ -456,11 +463,6 @@
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -524,23 +526,6 @@
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>install-tess4j</id>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<file>${project.basedir}/lib/tess4j.jar</file>
|
||||
<repositoryLayout>default</repositoryLayout>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<generatePom>true</generatePom>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>install-file</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -102,7 +102,6 @@ public class ValidationUtil {
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void validateHexColor(String s, String name, boolean nullable) throws JSONException {
|
||||
// TODO Do a real check
|
||||
ValidationUtil.validateLength(s, "name", 7, 7, nullable);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public class JerseyTestWebAppDescriptorFactory {
|
||||
.addFilter(TokenBasedSecurityFilter.class, "tokenBasedSecurityFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerRequestFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerResponseFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.initParam("com.sun.jersey.config.feature.logging.DisableEntitylogging", "true")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=5
|
||||
db.version=6
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -26,12 +24,10 @@ import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.event.ExtractFileAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
@@ -113,7 +109,7 @@ public class AppResource extends BaseResource {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
// TODO Change level by minLevel (returns all logs above)
|
||||
|
||||
// Get the memory appender
|
||||
Logger logger = Logger.getRootLogger();
|
||||
@@ -147,29 +143,6 @@ public class AppResource extends BaseResource {
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from all files again.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/extract")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchExtract() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Raise an extract file content event
|
||||
AppContext.getInstance().getAsyncEventBus().post(new ExtractFileAsyncEvent());
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
*
|
||||
@@ -197,7 +170,7 @@ public class AppResource extends BaseResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
* Clean storage.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
@@ -233,38 +206,4 @@ public class AppResource extends BaseResource {
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate file variations.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/file_variations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchFileVariations() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get all files
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
|
||||
// Generate variations for each file
|
||||
for (File file : fileList) {
|
||||
java.io.File originalFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
try {
|
||||
FileUtil.saveVariations(file, originalFile);
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Error generating file variations", e);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.TagDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
@@ -42,8 +43,10 @@ import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.Share;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
@@ -168,6 +171,7 @@ public class DocumentResource extends BaseResource {
|
||||
document.put("create_date", documentDto.getCreateTimestamp());
|
||||
document.put("shared", documentDto.getShared());
|
||||
document.put("language", documentDto.getLanguage());
|
||||
document.put("file_count", documentDto.getFileCount());
|
||||
|
||||
// Get tags
|
||||
List<TagDto> tagDtoList = tagDao.getByDocumentId(documentDto.getId());
|
||||
@@ -243,7 +247,9 @@ public class DocumentResource extends BaseResource {
|
||||
if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate());
|
||||
else documentCriteria.setCreateDateMin(date.toDate());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
// Invalid date, returns no documents
|
||||
if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0));
|
||||
else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2));
|
||||
}
|
||||
} else if (params[0].equals("at")) {
|
||||
// New specific date criteria
|
||||
@@ -262,7 +268,9 @@ public class DocumentResource extends BaseResource {
|
||||
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
// Invalid date, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
} else if (params[0].equals("shared")) {
|
||||
// New shared state criteria
|
||||
@@ -292,6 +300,9 @@ public class DocumentResource extends BaseResource {
|
||||
*
|
||||
* @param title Title
|
||||
* @param description Description
|
||||
* @param tags Tags
|
||||
* @param language Language
|
||||
* @param createDateStr Creation date
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@@ -455,21 +466,31 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
FileDao fileDao = new FileDao();
|
||||
Document document;
|
||||
List<File> fileList;
|
||||
try {
|
||||
document = documentDao.getDocument(id, principal.getId());
|
||||
fileList = fileDao.getByDocumentId(id);
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
}
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(document.getId());
|
||||
|
||||
// Raise file deleted events
|
||||
for (File file : fileList) {
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Raise a document deleted event
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setDocument(document);
|
||||
AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent);
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(document.getId());
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -20,22 +23,28 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.EncryptionUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
@@ -77,20 +86,30 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
Document document;
|
||||
User user;
|
||||
try {
|
||||
document = documentDao.getDocument(documentId, principal.getId());
|
||||
user = userDao.getById(principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
|
||||
FileDao fileDao = new FileDao();
|
||||
// Keep unencrypted data in memory, because we will need it two times
|
||||
byte[] fileData;
|
||||
try {
|
||||
fileData = ByteStreams.toByteArray(fileBodyPart.getValueAs(InputStream.class));
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("StreamError", "Error reading the input file", e);
|
||||
}
|
||||
InputStream fileInputStream = new ByteArrayInputStream(fileData);
|
||||
|
||||
// Validate mime type
|
||||
InputStream is = new BufferedInputStream(fileBodyPart.getValueAs(InputStream.class));
|
||||
String mimeType;
|
||||
try {
|
||||
mimeType = MimeTypeUtil.guessMimeType(is);
|
||||
mimeType = MimeTypeUtil.guessMimeType(fileInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("ErrorGuessMime", "Error guessing mime type", e);
|
||||
}
|
||||
@@ -113,12 +132,13 @@ public class FileResource extends BaseResource {
|
||||
String fileId = fileDao.create(file);
|
||||
|
||||
// Save the file
|
||||
FileUtil.save(is, file);
|
||||
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
||||
|
||||
// Raise a new file created event
|
||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||
fileCreatedAsyncEvent.setDocument(document);
|
||||
fileCreatedAsyncEvent.setFile(file);
|
||||
fileCreatedAsyncEvent.setInputStream(fileInputStream);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
||||
|
||||
// Always return ok
|
||||
@@ -249,14 +269,14 @@ public class FileResource extends BaseResource {
|
||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
fileDao.delete(file.getId());
|
||||
|
||||
// Raise a new file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent);
|
||||
|
||||
// Delete the file
|
||||
fileDao.delete(file.getId());
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
@@ -288,10 +308,12 @@ public class FileResource extends BaseResource {
|
||||
// Get the file
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
UserDao userDao = new UserDao();
|
||||
File file;
|
||||
Document document;
|
||||
try {
|
||||
file = fileDao.getFile(fileId);
|
||||
Document document = documentDao.getDocument(file.getDocumentId());
|
||||
document = documentDao.getDocument(file.getDocumentId());
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
@@ -306,21 +328,48 @@ public class FileResource extends BaseResource {
|
||||
// Get the stored file
|
||||
java.io.File storedfile;
|
||||
String mimeType;
|
||||
boolean decrypt = false;
|
||||
if (size != null) {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId + "_" + size).toFile();
|
||||
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());
|
||||
mimeType = MimeType.IMAGE_PNG;
|
||||
decrypt = false;
|
||||
}
|
||||
} else {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId).toFile();
|
||||
mimeType = file.getMimeType();
|
||||
decrypt = true; // Original files are encrypted
|
||||
}
|
||||
|
||||
return Response.ok(storedfile)
|
||||
// Stream the output and decrypt it if necessary
|
||||
StreamingOutput stream;
|
||||
User user = userDao.getById(document.getUserId());
|
||||
try {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
final InputStream responseInputStream = decrypt ?
|
||||
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
|
||||
|
||||
stream = new StreamingOutput() {
|
||||
@Override
|
||||
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
|
||||
try {
|
||||
ByteStreams.copy(responseInputStream, outputStream);
|
||||
} finally {
|
||||
responseInputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("FileError", "Error while reading the file", e);
|
||||
}
|
||||
|
||||
return Response.ok(stream)
|
||||
.header("Content-Type", mimeType)
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24 * 7))
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.dao.jpa.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.AuthenticationToken;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.EncryptionUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
@@ -28,6 +29,8 @@ import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -76,6 +79,11 @@ public class UserResource extends BaseResource {
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setEmail(email);
|
||||
try {
|
||||
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ServerException("PrivateKeyError", "Error while generating a private key", e);
|
||||
}
|
||||
user.setCreateDate(new Date());
|
||||
|
||||
if (localeId == null) {
|
||||
|
||||
1
docs-web/src/main/webapp/.gitignore
vendored
1
docs-web/src/main/webapp/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/sismicsdocs
|
||||
/.idea
|
||||
@@ -219,4 +219,5 @@ var App = angular.module('docs',
|
||||
.run(function($rootScope, $state, $stateParams) {
|
||||
$rootScope.$state = $state;
|
||||
$rootScope.$stateParams = $stateParams;
|
||||
$rootScope.pageTitle = 'Sismics Docs';
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Document edition controller.
|
||||
*/
|
||||
App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams, Restangular, Tag) {
|
||||
App.controller('DocumentEdit', function($rootScope, $scope, $q, $http, $state, $stateParams, Restangular, Tag) {
|
||||
// Alerts
|
||||
$scope.alerts = [];
|
||||
|
||||
@@ -39,18 +39,15 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
};
|
||||
|
||||
/**
|
||||
* In edit mode, load the current document.
|
||||
* Reset the form to add a new document.
|
||||
*/
|
||||
if ($scope.isEdit()) {
|
||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
||||
$scope.document = data;
|
||||
});
|
||||
} else {
|
||||
$scope.resetForm = function() {
|
||||
$scope.document = {
|
||||
tags: [],
|
||||
language: 'fra'
|
||||
};
|
||||
}
|
||||
$scope.newFiles = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Edit a document.
|
||||
@@ -84,16 +81,17 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
// When all files upload are over, move on
|
||||
var navigateNext = function() {
|
||||
if ($scope.isEdit()) {
|
||||
// Go back to the edited document
|
||||
$scope.pageDocuments();
|
||||
$state.transitionTo('document.view', { id: $stateParams.id });
|
||||
} else {
|
||||
// Reset the scope and stay here
|
||||
var fileUploadCount = _.size($scope.newFiles);
|
||||
$scope.alerts.unshift({
|
||||
type: 'success',
|
||||
msg: 'Document successfully added (with ' + fileUploadCount + ' file' + (fileUploadCount > 1 ? 's' : '') + ')'
|
||||
});
|
||||
$scope.document = { tags: [] };
|
||||
$scope.newFiles = [];
|
||||
$scope.resetForm();
|
||||
$scope.loadDocuments();
|
||||
}
|
||||
}
|
||||
@@ -102,6 +100,7 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
navigateNext();
|
||||
} else {
|
||||
$scope.fileIsUploading = true;
|
||||
$rootScope.pageTitle = '0% - Sismics Docs';
|
||||
|
||||
// Send a file from the input file array and return a promise
|
||||
var sendFile = function(key) {
|
||||
@@ -122,6 +121,7 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
|
||||
promiseFile.then(function() {
|
||||
$scope.fileProgress += 100 / _.size($scope.newFiles);
|
||||
$rootScope.pageTitle = Math.round($scope.fileProgress) + '% - Sismics Docs';
|
||||
});
|
||||
|
||||
return promiseFile;
|
||||
@@ -136,6 +136,7 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
} else {
|
||||
$scope.fileIsUploading = false;
|
||||
$scope.fileProgress = 0;
|
||||
$rootScope.pageTitle = 'Sismics Docs';
|
||||
navigateNext();
|
||||
}
|
||||
};
|
||||
@@ -154,4 +155,15 @@ App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams,
|
||||
$state.transitionTo('document.default');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In edit mode, load the current document.
|
||||
*/
|
||||
if ($scope.isEdit()) {
|
||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
||||
$scope.document = data;
|
||||
});
|
||||
} else {
|
||||
$scope.resetForm();
|
||||
}
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
App.controller('FileView', function($dialog, $state, $stateParams) {
|
||||
var dialog = $dialog.dialog({
|
||||
keyboard: true,
|
||||
dialogClass: 'modal modal-fileview',
|
||||
templateUrl: 'partial/docs/file.view.html',
|
||||
controller: function($scope, $state, $stateParams, Restangular, dialog) {
|
||||
// Load files
|
||||
|
||||
@@ -6,6 +6,42 @@
|
||||
App.controller('Navigation', function($scope, $http, $state, $rootScope, User, Restangular) {
|
||||
$rootScope.userInfo = User.userInfo();
|
||||
|
||||
// Last time when the errors logs was checked
|
||||
$scope.lastLogCheck = new Date().getTime();
|
||||
|
||||
// Number of errors logs
|
||||
$scope.errorNumber = 0;
|
||||
|
||||
// Check repeatedly if there is a new error log
|
||||
setInterval(function() {
|
||||
$scope.$apply(function() {
|
||||
Restangular.one('app/log').get({
|
||||
// Error count will be wrong if there is more than 10 errors in 60 seconds
|
||||
limit: 10,
|
||||
level: 'ERROR'
|
||||
}).then(function(data) {
|
||||
// Add new errors
|
||||
$scope.errorNumber += _.reduce(data.logs, function(number, log) {
|
||||
if (log.date > $scope.lastLogCheck) {
|
||||
return ++number; // It's a new error
|
||||
}
|
||||
return number; // Not a new error
|
||||
}, 0);
|
||||
|
||||
// Update last check timestamp
|
||||
$scope.lastLogCheck = new Date().getTime();
|
||||
});
|
||||
})
|
||||
}, 60000);
|
||||
|
||||
/**
|
||||
* Navigate to error logs.
|
||||
*/
|
||||
$scope.openLogs = function() {
|
||||
$scope.errorNumber = 0;
|
||||
$state.transitionTo('settings.log');
|
||||
};
|
||||
|
||||
/**
|
||||
* User logout.
|
||||
*/
|
||||
@@ -17,6 +53,9 @@ App.controller('Navigation', function($scope, $http, $state, $rootScope, User, R
|
||||
$event.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if at least an asynchronous request is in progress.
|
||||
*/
|
||||
$scope.isLoading = function() {
|
||||
return $http.pendingRequests.length > 0;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,6 @@ App.controller('Tag', function($scope, $dialog, $state, Tag, Restangular) {
|
||||
* Add a tag.
|
||||
*/
|
||||
$scope.addTag = function() {
|
||||
// TODO Check if the tag don't already exists
|
||||
Restangular.one('tag').put($scope.tag).then(function(data) {
|
||||
$scope.tags.push({ id: data.id, name: $scope.tag.name, color: $scope.tag.color });
|
||||
$scope.tag = { name: '', color: '#3a87ad' };
|
||||
|
||||
@@ -10,7 +10,8 @@ App.directive('selectTag', function() {
|
||||
replace: true,
|
||||
scope: {
|
||||
tags: '=',
|
||||
ref: '@'
|
||||
ref: '@',
|
||||
ngDisabled: '='
|
||||
},
|
||||
controller: function($scope, Tag) {
|
||||
// Retrieve tags
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
App.controller('FileView', function($dialog, $state, $stateParams) {
|
||||
var dialog = $dialog.dialog({
|
||||
keyboard: true,
|
||||
dialogClass: 'modal modal-fileview',
|
||||
templateUrl: 'partial/share/file.view.html',
|
||||
controller: function($scope, $state, $stateParams, Restangular, dialog) {
|
||||
// Load files
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="docs">
|
||||
<head>
|
||||
<title>Sismics Docs</title>
|
||||
<title ng-bind-template="{{ pageTitle }}">Sismics Docs</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" href="favicon.png" />
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="navbar" ng-controller="Navigation">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<div class="brand loader" ng-class="{hide: !isLoading() }">
|
||||
<div class="brand loader hidden-phone" ng-class="{hide: !isLoading() }">
|
||||
<img src="img/loader.gif" />
|
||||
</div>
|
||||
|
||||
@@ -72,6 +72,11 @@
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-right" ng-show="!userInfo.anonymous">
|
||||
<li ng-show="errorNumber > 0">
|
||||
<a href="#/settings/log" ng-click="openLogs()" class="nav-text-error">
|
||||
<span class="icon-warning-sign"></span> {{ errorNumber }} new error{{ errorNumber > 1 ? 's' : '' }}
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: $uiRoute}" ui-route="/settings.*"><a href="#/settings/account"><span class="icon-cog"></span><span class="hidden-phone"> Settings</span></a></li>
|
||||
<li><a href="#" ng-click="logout($event)"><span class="icon-off"></span><span class="hidden-phone"> Logout</span></a></li>
|
||||
</ul>
|
||||
|
||||
1
docs-web/src/main/webapp/lib/jquery.js
vendored
1
docs-web/src/main/webapp/lib/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<ul class="inline">
|
||||
<li ng-repeat="tag in tags"><span class="label label-info" ng-style="{ 'background': tag.color }">{{ tag.name }} <span class="icon-remove icon-white" ng-click="deleteTag(tag)"></span></span></li>
|
||||
<li ng-repeat="tag in tags"><span class="label label-info" ng-style="{ 'background': tag.color }">{{ tag.name }} <span class="icon-remove icon-white" ng-click="deleteTag(tag)" ng-show="!ngDisabled"></span></span></li>
|
||||
</ul>
|
||||
<input class="span12" type="text" id="{{ ref }}" placeholder="Type a tag" ng-model="input"
|
||||
<input class="span12" type="text" id="{{ ref }}" placeholder="Type a tag" ng-model="input" ng-disabled="ngDisabled"
|
||||
autocomplete="off" typeahead="tag.name for tag in allTags | filter: $viewValue" typeahead-on-select="addTag()" />
|
||||
</div>
|
||||
@@ -1,56 +1,63 @@
|
||||
<form class="form-horizontal" name="documentForm">
|
||||
<div class="control-group" ng-class="{ error: !documentForm.title.$valid }">
|
||||
<label class="control-label" for="inputTitle">Title</label>
|
||||
<div class="controls">
|
||||
<input required ng-maxlength="100" class="input-block-level" type="text" id="inputTitle"
|
||||
placeholder="Title" name="title" ng-model="document.title" autocomplete="off"
|
||||
typeahead="document for document in getTitleTypeahead($viewValue) | filter: $viewValue"
|
||||
typeahead-wait-ms="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-class="{ error: !documentForm.description.$valid }">
|
||||
<label class="control-label" for="inputDescription">Description</label>
|
||||
<div class="controls">
|
||||
<textarea ng-maxlength="4000" class="input-block-level" rows="5" id="inputDescription" name="description" ng-model="document.description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputCreateDate">Creation date</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="inputCreateDate" ng-readonly="true" datepicker-popup="yyyy-MM-dd" ng-model="document.create_date" starting-day="1" show-weeks="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputCreateDate">Language</label>
|
||||
<div class="controls">
|
||||
<select id="inputLanguage" ng-model="document.language">
|
||||
<option value="fra">French</option>
|
||||
<option value="eng">English</option>
|
||||
<option value="jpn">Japanese</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputFiles">New files</label>
|
||||
<div class="controls">
|
||||
<file class="input-block-level" id="inputFiles" multiple="multiple" ng-model="newFiles" accept="image/png,image/jpg,image/jpeg,image/gif,application/pdf" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputTags">Tags</label>
|
||||
<div class="controls">
|
||||
<select-tag tags="document.tags" class="input-block-level" ref="inputTags" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="!documentForm.$valid || fileIsUploading" ng-click="edit()">{{ isEdit() ? 'Edit' : 'Add' }}</button>
|
||||
<button type="submit" class="btn" ng-click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
<img src="img/loader.gif" ng-show="!document && isEdit()" />
|
||||
|
||||
<div class="row-fluid" ng-show="fileIsUploading">
|
||||
<h4>Uploading files...</h4>
|
||||
<div class="span6"><progress percent="fileProgress" class="progress-info active"></progress></div>
|
||||
<div ng-show="document || !isEdit()">
|
||||
<form class="form-horizontal" name="documentForm">
|
||||
<div class="control-group" ng-class="{ error: !documentForm.title.$valid }">
|
||||
<label class="control-label" for="inputTitle">Title</label>
|
||||
<div class="controls">
|
||||
<input required ng-maxlength="100" class="input-block-level" type="text" id="inputTitle"
|
||||
placeholder="Title" name="title" ng-model="document.title" autocomplete="off"
|
||||
typeahead="document for document in getTitleTypeahead($viewValue) | filter: $viewValue"
|
||||
typeahead-wait-ms="200" ng-disabled="fileIsUploading" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-class="{ error: !documentForm.description.$valid }">
|
||||
<label class="control-label" for="inputDescription">Description</label>
|
||||
<div class="controls">
|
||||
<textarea ng-maxlength="4000" class="input-block-level" rows="5" id="inputDescription"
|
||||
name="description" ng-model="document.description" ng-disabled="fileIsUploading"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputCreateDate">Creation date</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="inputCreateDate" ng-readonly="true" datepicker-popup="yyyy-MM-dd"
|
||||
ng-model="document.create_date" starting-day="1" show-weeks="false" ng-disabled="fileIsUploading" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputCreateDate">Language</label>
|
||||
<div class="controls">
|
||||
<select id="inputLanguage" ng-model="document.language" ng-disabled="fileIsUploading">
|
||||
<option value="fra">French</option>
|
||||
<option value="eng">English</option>
|
||||
<option value="jpn">Japanese</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputFiles">New files</label>
|
||||
<div class="controls">
|
||||
<file class="input-block-level" id="inputFiles" multiple="multiple" ng-model="newFiles"
|
||||
accept="image/png,image/jpg,image/jpeg,image/gif,application/pdf" ng-disabled="fileIsUploading" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="inputTags">Tags</label>
|
||||
<div class="controls">
|
||||
<select-tag tags="document.tags" class="input-block-level" ref="inputTags" ng-disabled="fileIsUploading" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="!documentForm.$valid || fileIsUploading" ng-click="edit()">{{ isEdit() ? 'Edit' : 'Add' }}</button>
|
||||
<button type="submit" class="btn" ng-click="cancel()" ng-disabled="fileIsUploading">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row-fluid" ng-show="fileIsUploading">
|
||||
<h4>Uploading files...</h4>
|
||||
<div class="span6"><progress percent="fileProgress" class="progress-info active"></progress></div>
|
||||
</div>
|
||||
|
||||
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{ alert.msg }}</alert>
|
||||
</div>
|
||||
|
||||
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{ alert.msg }}</alert>
|
||||
@@ -30,7 +30,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents">
|
||||
<td>{{ document.title }} <span class="icon-share" ng-if="document.shared" tooltip="Shared"></span></td>
|
||||
<td>{{ document.title }} ({{ document.file_count }})<span class="icon-share" ng-if="document.shared" tooltip="Shared"></span></td>
|
||||
<td>{{ document.create_date | date: 'yyyy-MM-dd' }}</td>
|
||||
<td class="hidden-phone cell-tags">
|
||||
<div class="tags">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<img src="img/loader.gif" ng-if="!document" />
|
||||
<img src="img/loader.gif" ng-show="!document" />
|
||||
|
||||
<div ng-if="document">
|
||||
<div ng-show="document">
|
||||
<div class="text-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger" ng-click="deleteDocument(document)"><span class="icon-trash icon-white"></span> Delete</button>
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small> <img ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" /></h1>
|
||||
<h1>{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small> <img ng-if="document" ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" /></h1>
|
||||
<p>
|
||||
<button class="btn btn-small btn-inverse" ng-click="share()"><span class="icon-share icon-white"></span> Share</button>
|
||||
<button class="btn btn-small" ng-repeat="share in document.shares" ng-click="showShare(share)"><span class="icon-ok"></span> {{ share.name ? share.name : 'shared' }}</button>
|
||||
|
||||
@@ -1,62 +1,66 @@
|
||||
<h2 ng-show="isEdit()">Edit
|
||||
<small>"{{ user.username }}"</small>
|
||||
</h2>
|
||||
<h2 ng-show="!isEdit()">Add
|
||||
<small>user</small>
|
||||
</h2>
|
||||
<form class="form-horizontal" name="editUserForm" novalidate>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.username.$valid, success: editUserForm.username.$valid }">
|
||||
<label class="control-label" for="inputUsername">Username</label>
|
||||
<img src="img/loader.gif" ng-show="!user && isEdit()" />
|
||||
|
||||
<div class="controls">
|
||||
<input name="username" type="text" id="inputUsername" required ng-disabled="isEdit()"
|
||||
ng-minlength="3" ng-maxlength="50" placeholder="Username" ng-model="user.username"/>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.maxlength">Too long</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.email.$valid, success: editUserForm.email.$valid }">
|
||||
<label class="control-label" for="inputEmail">E-mail</label>
|
||||
<div ng-show="user || !isEdit()">
|
||||
<h2 ng-show="isEdit()">Edit
|
||||
<small>"{{ user.username }}"</small>
|
||||
</h2>
|
||||
<h2 ng-show="!isEdit()">Add
|
||||
<small>user</small>
|
||||
</h2>
|
||||
<form class="form-horizontal" name="editUserForm" novalidate>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.username.$valid, success: editUserForm.username.$valid }">
|
||||
<label class="control-label" for="inputUsername">Username</label>
|
||||
|
||||
<div class="controls">
|
||||
<input name="email" type="email" id="inputEmail" required
|
||||
ng-minlength="3" ng-maxlength="50" placeholder="E-mail" ng-model="user.email"/>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.email">Must be a valid e-mail</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.maxlength">Too long</span>
|
||||
<div class="controls">
|
||||
<input name="username" type="text" id="inputUsername" required ng-disabled="isEdit()"
|
||||
ng-minlength="3" ng-maxlength="50" placeholder="Username" ng-model="user.username"/>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.username.$error.maxlength">Too long</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.password.$valid, success: editUserForm.password.$valid }">
|
||||
<label class="control-label" for="inputPassword">Password</label>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.email.$valid, success: editUserForm.email.$valid }">
|
||||
<label class="control-label" for="inputEmail">E-mail</label>
|
||||
|
||||
<div class="controls">
|
||||
<input name="password" type="password" id="inputPassword" ng-required="!isEdit()"
|
||||
ng-minlength="8" ng-maxlength="50" placeholder="Password" ng-model="user.password"/>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.maxlength">Too long</span>
|
||||
<div class="controls">
|
||||
<input name="email" type="email" id="inputEmail" required
|
||||
ng-minlength="3" ng-maxlength="50" placeholder="E-mail" ng-model="user.email"/>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.email">Must be a valid e-mail</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.email.$error.maxlength">Too long</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group"
|
||||
ng-class="{ error: !editUserForm.passwordconfirm.$valid, success: editUserForm.passwordconfirm.$valid }">
|
||||
<label class="control-label" for="inputPasswordConfirm">Password (confirm)</label>
|
||||
<div class="control-group" ng-class="{ error: !editUserForm.password.$valid, success: editUserForm.password.$valid }">
|
||||
<label class="control-label" for="inputPassword">Password</label>
|
||||
|
||||
<div class="controls">
|
||||
<input name="passwordconfirm" type="password" id="inputPasswordConfirm" ng-required="!isEdit()"
|
||||
ui-validate="'$value == user.password'" ui-validate-watch="'user.password'"
|
||||
placeholder="Password (confirm)" ng-model="user.passwordconfirm"/>
|
||||
<span class="help-inline" ng-show="editUserForm.passwordconfirm.$error.validator">Password and password confirmation must match</span>
|
||||
<div class="controls">
|
||||
<input name="password" type="password" id="inputPassword" ng-required="!isEdit()"
|
||||
ng-minlength="8" ng-maxlength="50" placeholder="Password" ng-model="user.password"/>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.required">Required</span>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.minlength">Too short</span>
|
||||
<span class="help-inline" ng-show="editUserForm.password.$error.maxlength">Too long</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" ng-click="edit()" ng-disabled="!editUserForm.$valid">
|
||||
<span class="icon-pencil icon-white"></span> {{ isEdit() ? 'Edit' : 'Add' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="remove()" ng-show="isEdit()">
|
||||
<span class="icon-trash icon-white"></span> Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{ alert.msg }}</alert>
|
||||
<div class="control-group"
|
||||
ng-class="{ error: !editUserForm.passwordconfirm.$valid, success: editUserForm.passwordconfirm.$valid }">
|
||||
<label class="control-label" for="inputPasswordConfirm">Password (confirm)</label>
|
||||
|
||||
<div class="controls">
|
||||
<input name="passwordconfirm" type="password" id="inputPasswordConfirm" ng-required="!isEdit()"
|
||||
ui-validate="'$value == user.password'" ui-validate-watch="'user.password'"
|
||||
placeholder="Password (confirm)" ng-model="user.passwordconfirm"/>
|
||||
<span class="help-inline" ng-show="editUserForm.passwordconfirm.$error.validator">Password and password confirmation must match</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" ng-click="edit()" ng-disabled="!editUserForm.$valid">
|
||||
<span class="icon-pencil icon-white"></span> {{ isEdit() ? 'Edit' : 'Add' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="remove()" ng-show="isEdit()">
|
||||
<span class="icon-trash icon-white"></span> Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{ alert.msg }}</alert>
|
||||
</div>
|
||||
@@ -80,6 +80,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// File view
|
||||
.modal-fileview {
|
||||
top: 2%;
|
||||
max-height: 96%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
// File thumbnails
|
||||
.thumbnail-file {
|
||||
cursor: pointer;
|
||||
@@ -134,3 +141,7 @@ input[readonly].share-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-text-error {
|
||||
color: #b94a48 !important;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=5
|
||||
db.version=6
|
||||
@@ -43,13 +43,6 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
||||
Assert.assertEquals(0, json.getInt("document_count"));
|
||||
|
||||
// OCR-ize all files
|
||||
appResource = resource().path("/app/batch/extract");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
response = appResource.post(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
|
||||
// Rebuild Lucene index
|
||||
appResource = resource().path("/app/batch/reindex");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
@@ -83,7 +76,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONObject json = response.getEntity(JSONObject.class);
|
||||
JSONArray logs = json.getJSONArray("logs");
|
||||
Assert.assertTrue(logs.length() == 10);
|
||||
Assert.assertTrue(logs.length() > 0);
|
||||
Long date1 = logs.optJSONObject(0).optLong("date");
|
||||
Long date2 = logs.optJSONObject(9).optLong("date");
|
||||
Assert.assertTrue(date1 > date2);
|
||||
@@ -99,7 +92,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
logs = json.getJSONArray("logs");
|
||||
Assert.assertTrue(logs.length() == 10);
|
||||
Assert.assertTrue(logs.length() > 0);
|
||||
Long date3 = logs.optJSONObject(0).optLong("date");
|
||||
Long date4 = logs.optJSONObject(9).optLong("date");
|
||||
Assert.assertTrue(date3 > date4);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sismics.docs.rest;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
@@ -10,13 +11,15 @@ import javax.ws.rs.core.MediaType;
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
@@ -32,11 +35,10 @@ import com.sun.jersey.multipart.FormDataMultiPart;
|
||||
public class TestDocumentResource extends BaseJerseyTest {
|
||||
/**
|
||||
* Test the document resource.
|
||||
*
|
||||
* @throws JSONException
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testDocumentResource() throws JSONException {
|
||||
public void testDocumentResource() throws Exception {
|
||||
// Login document1
|
||||
clientUtil.createUser("document1");
|
||||
String document1Token = clientUtil.login("document1");
|
||||
@@ -82,6 +84,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
String file1Id = json.getString("id");
|
||||
|
||||
// Share this document
|
||||
WebResource fileShareResource = resource().path("/share");
|
||||
@@ -106,183 +109,36 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals("eng", documents.getJSONObject(0).getString("language"));
|
||||
Assert.assertEquals(1, documents.getJSONObject(0).getInt("file_count"));
|
||||
Assert.assertEquals(1, tags.length());
|
||||
Assert.assertEquals(tag1Id, tags.getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals("SuperTag", tags.getJSONObject(0).getString("name"));
|
||||
Assert.assertEquals("#ffff00", tags.getJSONObject(0).getString("color"));
|
||||
|
||||
// Search documents by query in full content
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "full:uranium");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
|
||||
// Search documents by query in full content
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "full:title");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
|
||||
// Search documents by query in title/description
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "title");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
|
||||
// Search documents by query in title/description
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "description");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
|
||||
// Search documents by specific date
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "at:" + DateTimeFormat.forPattern("yyyy-MM").print(new Date().getTime()));
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
|
||||
// Search documents by date span
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "after:2010 before:2040-08");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
|
||||
// Search documents by tag
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "tag:super");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
|
||||
// Search documents by shared status
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "shared:yes");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals(true, documents.getJSONObject(0).getBoolean("shared"));
|
||||
|
||||
// Search documents by language
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "lang:eng");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals("eng", documents.getJSONObject(0).getString("language"));
|
||||
|
||||
// Search documents with multiple criteria
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "after:2010 before:2040-08 tag:super shared:yes lang:eng title description full:uranium");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 1);
|
||||
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
|
||||
// Search documents
|
||||
Assert.assertEquals(1, searchDocuments("full:uranium full:einstein", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("full:title", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("title", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("super description", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("at:" + DateTimeFormat.forPattern("yyyy").print(new Date().getTime()), document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("at:" + DateTimeFormat.forPattern("yyyy-MM").print(new Date().getTime()), document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("at:" + DateTimeFormat.forPattern("yyyy-MM-dd").print(new Date().getTime()), document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("after:2010 before:2040-08", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("tag:super", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("shared:yes", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("lang:eng", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("after:2010 before:2040-08 tag:super shared:yes lang:eng title description full:uranium", document1Token));
|
||||
|
||||
// Search documents (nothing)
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "random");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
|
||||
// Search documents (nothing)
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "full:random");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
|
||||
// Search documents (nothing)
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "after:2010 before:2011-05-20");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
|
||||
// Search documents (nothing)
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "tag:Nop");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
|
||||
// Search documents (nothing)
|
||||
documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", "lang:fra");
|
||||
response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
documents = json.getJSONArray("documents");
|
||||
Assert.assertTrue(documents.length() == 0);
|
||||
Assert.assertEquals(0, searchDocuments("random", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("full:random", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("after:2010 before:2011-05-20", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("at:2040-05-35", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("after:2010-18 before:2040-05-38", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("after:2010-18", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("before:2040-05-38", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("tag:Nop", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("lang:fra", document1Token));
|
||||
|
||||
// Get a document
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
@@ -348,6 +204,14 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
json = response.getEntity(JSONObject.class);
|
||||
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();
|
||||
Assert.assertFalse(storedFile.exists());
|
||||
Assert.assertFalse(webFile.exists());
|
||||
Assert.assertFalse(thumbnailFile.exists());
|
||||
|
||||
// Get a document (KO)
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
@@ -356,6 +220,25 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search documents and returns the number found.
|
||||
*
|
||||
* @param query Search query
|
||||
* @param token Authentication token
|
||||
* @return Number of documents found
|
||||
* @throws Exception
|
||||
*/
|
||||
private int searchDocuments(String query, String token) throws Exception {
|
||||
WebResource documentResource = resource().path("/document/list");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(token));
|
||||
MultivaluedMapImpl getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("search", query);
|
||||
ClientResponse response = documentResource.queryParams(getParams).get(ClientResponse.class);
|
||||
JSONObject json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
return json.getJSONArray("documents").length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test PDF extraction.
|
||||
*
|
||||
@@ -416,6 +299,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
InputStream is = response.getEntityInputStream();
|
||||
byte[] fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(33691, fileBytes.length);
|
||||
Assert.assertTrue(fileBytes.length > 0); // Images rendered from PDF differ in size from OS to OS due to font issues
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@@ -15,6 +16,8 @@ import org.junit.Test;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
@@ -88,6 +91,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
InputStream is = response.getEntityInputStream();
|
||||
byte[] fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(163510, fileBytes.length);
|
||||
|
||||
// Get the thumbnail data
|
||||
@@ -99,6 +103,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(41935, fileBytes.length);
|
||||
|
||||
// Get the web data
|
||||
@@ -110,45 +115,14 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(551084, fileBytes.length);
|
||||
|
||||
// Regenerate file variations
|
||||
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
|
||||
WebResource appResource = resource().path("/app/batch/file_variations");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
response = appResource.post(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Get the file data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
response = fileResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(163510, fileBytes.length);
|
||||
|
||||
// Get the thumbnail data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("size", "thumb");
|
||||
response = fileResource.queryParams(getParams).get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(41935, fileBytes.length);
|
||||
|
||||
// Get the web data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("size", "web");
|
||||
response = fileResource.queryParams(getParams).get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(551084, fileBytes.length);
|
||||
// Check that the files are not readable directly from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
InputStream storedFileInputStream = new BufferedInputStream(new FileInputStream(storedFile));
|
||||
Assert.assertNull(MimeTypeUtil.guessMimeType(storedFileInputStream));
|
||||
storedFileInputStream.close();
|
||||
|
||||
// Get all files from a document
|
||||
fileResource = resource().path("/file/list");
|
||||
@@ -195,7 +169,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check that files are deleted from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
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());
|
||||
|
||||
@@ -7,3 +7,4 @@ log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=DEBUG
|
||||
log4j.logger.org.hibernate=INFO
|
||||
log4j.logger.org.apache.pdfbox=INFO
|
||||
Reference in New Issue
Block a user