1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-14 10:16:21 +00:00

201 Commits
v1.3 ... v1.4

Author SHA1 Message Date
jendib
c695572b28 Release 1.4 2016-05-09 22:25:12 +02:00
jendib
79141edf70 Closes #97: Handle write permission in #/tag and #/tag/id 2016-05-09 22:09:29 +02:00
jendib
b1e58396d1 Closes #98: Fix inherited permissions table 2016-05-09 21:53:15 +02:00
jendib
b9cd113dc0 Closes #99: Update /app/batch/clean_storage & /app/batch/acl_tags 2016-05-09 19:11:44 +02:00
jendib
4a512af178 Bump version to 1.4-SNAPSHOT 2016-05-09 15:23:02 +02:00
jendib
9506e9b8b4 UI: minor spacing 2016-05-09 10:28:20 +02:00
jendib
3ff41d2002 Fix inherited ACLs displayed on documents 2016-05-08 23:40:08 +02:00
jendib
f41dafe76d Theme images expiration date 2016-05-08 23:31:33 +02:00
jendib
6f89a50fe5 Fix batch for ACLs on tags 2016-05-08 23:20:58 +02:00
jendib
e234440ce6 Closes #93: Edit tag color and title in #/tag/id 2016-05-08 23:05:44 +02:00
jendib
26685334a1 Closes #79: UI: Change background and logo image 2016-05-08 18:57:32 +02:00
jendib
4d79dd7076 #79: Change background and logo image 2016-05-08 17:25:21 +02:00
jendib
f5394534f7 #79: Change custom CSS and app name 2016-05-08 15:38:47 +02:00
jendib
faa66e01b6 Tag color in #/tag/id 2016-05-08 13:47:35 +02:00
jendib
bf4cb02de5 Closes #91: Display ACL inherited from tags in document permissions 2016-05-08 13:45:46 +02:00
jendib
642b9a63d3 Cleanup ACL checks 2016-05-08 12:14:06 +02:00
Benjamin Gamard
1ed7422171 Merge pull request #92 from sismics/tags_acl
Tags as source for ACL
2016-05-08 01:11:32 +02:00
jendib
3dd8a52f7d #83: Fix test for tag parent 2016-05-08 01:03:15 +02:00
jendib
a55c55bbdb Closes #83: Edit ACLs for tags in UI + batch for old DB 2016-05-08 00:46:32 +02:00
jendib
b851fd0ecc #83: GET /tag/id 2016-05-07 18:20:01 +02:00
jendib
c8f7fe15ef #83: Don't return non-visible tag parent 2016-05-07 15:53:13 +02:00
jendib
73133f5ba5 #83: Remove GET /tag/stats 2016-05-07 15:41:19 +02:00
jendib
eaf2e816b4 Imports 2016-05-06 00:55:00 +02:00
jendib
62020864ef #83: Fix ACL resource test 2016-05-06 00:49:41 +02:00
jendib
f12e3ec663 #83: Access documents by a shared tag 2016-05-06 00:36:54 +02:00
jendib
5226df53a2 Fix pom.xml after removing docs-parent 2016-05-05 22:43:18 +02:00
jendib
a59c67d774 docs-parent folder removed 2016-05-05 22:37:27 +02:00
jendib
1b1d5e9b4c #83: Use ACLs for tag operations 2016-05-05 22:36:53 +02:00
jendib
37fc2d09bb Entropy source for Travis 2016-05-05 22:18:07 +02:00
jendib
bc94466cf7 Entropy source for Travis 2016-05-05 22:16:04 +02:00
jendib
6af7b6fce9 Reduce tests verbosity 2016-05-05 21:59:50 +02:00
jendib
f2ae899938 Don't dump entities in JUnit 2016-05-05 21:33:31 +02:00
jendib
c398a3c4f5 #83: Tag name duplicates now allowed 2016-05-05 21:12:14 +02:00
jendib
27027ec412 #83: Tag DAO refactoring 2016-05-05 02:34:33 +02:00
jendib
ddf9e83a9b #83: Permission check for tags 2016-05-01 22:03:39 +02:00
jendib
0f661e5a34 #83: Handles tags as source ACL for single document 2016-04-30 02:17:04 +02:00
jendib
09a53d5c4e #83: Handles tags as source ACL in GET /document/list 2016-04-30 01:52:24 +02:00
jendib
542ab737a2 #79: POST /theme, GET /theme 2016-04-27 00:05:25 +02:00
jendib
6e1276293f #79: Change theme color UI 2016-04-23 23:47:33 +02:00
jendib
4e768e9103 #79: POST /theme/color to change the main color 2016-04-18 00:00:46 +02:00
jendib
55cdca0c7d Android: allow install on sdcard 2016-04-17 23:54:58 +02:00
jendib
9b52395786 Fix dependencies 2016-04-16 20:54:23 +02:00
jendib
50b02c800c git ignore 2016-04-14 20:51:23 +02:00
jendib
2bdae5ea5c cleanup 2016-04-14 20:49:39 +02:00
jendib
c49827ce25 Closes #90: Android: Fill audit log date 2016-04-14 00:43:29 +02:00
jendib
64db701498 Closes #84: Android: Ask a validation code on login 2016-04-14 00:37:01 +02:00
jendib
e16ce4b4f1 Init e2e testing 2016-04-13 01:30:02 +02:00
jendib
77d1e87fdb Android: upgrade Gradle 2016-04-13 01:29:27 +02:00
jendib
7d7adeeca0 #79: CSS generator 2016-04-13 01:29:03 +02:00
jendib
8ad9c529b6 #79: Resource to generate a dynamic CSS 2016-04-09 21:23:55 +02:00
jendib
274512a58e Fix if a file is deleted before text extraction is finished 2016-03-24 00:41:31 +01:00
jendib
ef16561272 Fix PDF export if description is null 2016-03-24 00:35:53 +01:00
jendib
98350860eb #84: Ask for a TOTP validation code (web UI) 2016-03-24 00:03:29 +01:00
jendib
1343948d33 #84: Enable/disable TOTP in UI 2016-03-23 23:48:54 +01:00
jendib
e616add75a #84: Init 2FA view + controllers refactoring 2016-03-23 22:31:09 +01:00
jendib
b33b7115ef #84: POST /user/disable_totp 2016-03-23 22:03:45 +01:00
jendib
fb0bb62eaf #84: TOTP key generation and validation code checking on login 2016-03-22 23:08:49 +01:00
jendib
5f84da61c8 Closes #88: XHR line loader with ngProgress 2016-03-22 22:35:42 +01:00
jendib
6e6babd2e3 #84: Import sources from https://github.com/wstrange/GoogleAuth 2016-03-22 22:15:19 +01:00
Benjamin Gamard
b28e08e2c7 Update README.md 2016-03-22 14:21:10 +01:00
jendib
718728a672 #84: Generate TOTP secret key 2016-03-22 01:18:18 +01:00
jendib
5de77e35dc Closes #87: Fix delete vocabulary after adding it 2016-03-22 00:38:56 +01:00
jendib
5a41e9555e Closes #82: Add role to groups 2016-03-20 22:18:58 +01:00
jendib
6598b585a2 Closes #18: Android: Group profile 2016-03-20 21:44:53 +01:00
jendib
a81474b40a Fix authentication cookie extraction 2016-03-20 19:39:52 +01:00
jendib
ee159f5b36 #18: Groups profile (web) 2016-03-20 19:12:38 +01:00
jendib
ced64a5d1f #18: Add/remove users from groups 2016-03-20 17:30:36 +01:00
jendib
689a4e6aae #18: Add/update/delete groups 2016-03-20 15:09:34 +01:00
jendib
21b3ba2bf6 #18: Handle new audit log for groups, filter users by group 2016-03-20 12:20:12 +01:00
jendib
7be2e1b9e5 #18: Add/display group ACL in web UI 2016-03-20 01:20:37 +01:00
jendib
c1c2228937 #18: GET /group + fix JUnit 2016-03-19 23:42:36 +01:00
jendib
3b9a66d1d8 #18: administrators group 2016-03-19 19:56:02 +01:00
jendib
a5ce5bf9ec #18: Group resource, groups handling in ACL, groups returned in users 2016-03-19 19:41:28 +01:00
jendib
43a1575187 #18: PUT /group 2016-03-17 01:43:10 +01:00
jendib
eb5f207cc1 #18: Group and user group DB model 2016-03-16 22:14:25 +01:00
jendib
de3f055323 #18: ACL check for groups 2016-03-15 22:44:50 +01:00
jendib
6012cdd9a5 fix junit 2016-03-15 21:25:47 +01:00
jendib
0fab8ff935 Nullable document metadata can be emptied 2016-03-15 00:58:55 +01:00
jendib
00ee2d3bf6 Closes #77: Metadata in PDF export 2016-03-15 00:43:27 +01:00
jendib
c2a2e9f585 DAO/event refactoring 2016-03-14 01:39:29 +01:00
jendib
31fff7e021 Update TODO 2016-03-13 23:18:33 +01:00
jendib
0dda01269f Search logs by min level instead of exact level 2016-03-13 23:13:12 +01:00
jendib
d58b0e8f74 Closes #73: Android: User profile 2016-03-13 19:23:52 +01:00
jendib
1bbb21c7c6 #73: Android: Display creator 2016-03-12 23:39:57 +01:00
jendib
24713f54e2 Close #72: Android: Audit log 2016-03-12 23:25:31 +01:00
jendib
5e2bd76e10 Close #71: Android: Advanced search by creator 2016-03-12 21:27:53 +01:00
jendib
78d4b5797b Information when the current user can't access a document 2016-03-12 20:31:39 +01:00
jendib
ff91521a67 Closes #67: Relations between document (client-side) 2016-03-12 20:29:02 +01:00
jendib
0525754337 #67: relations between documents (server-side) 2016-03-06 21:06:23 +01:00
jendib
ca8c525de0 Closes #80: Android: Use support design library for FAB 2016-03-06 14:51:19 +01:00
jendib
1e7d2fcfd9 Upgrade jersey, joda-time, hibernate 2016-03-05 19:53:41 +01:00
jendib
7e983bebb9 #67: Relations database schema 2016-03-03 23:54:48 +01:00
jendib
f927193ae9 Closes #78: login page design 2016-03-03 23:16:50 +01:00
jendib
a102bf04f4 #68: Contributors in share UI 2016-03-02 00:52:49 +01:00
Benjamin Gamard
919948489d Merge pull request #75 from sismics/lucene5
Closes #68: Display contributors in UI
2016-03-02 00:38:03 +01:00
jendib
12efd5c11f Closes #68: Display contributors in UI 2016-03-02 00:35:38 +01:00
Benjamin Gamard
25a2144b31 Merge pull request #74 from sismics/lucene5
Migration to Lucene 5
2016-03-01 23:54:17 +01:00
jendib
59682b5ba6 Closes #62: logs for index checking, explicit commit on close 2016-03-01 23:52:15 +01:00
Jean-Marc Tremeaux
7deaeca7b5 Make Docker use a volume instead of a volume container 2016-03-01 11:32:07 +01:00
jendib
a7a6adfa34 #62: Rebuild index if too old or corrupted 2016-03-01 01:24:26 +01:00
jendib
7f19f8c112 #62: Migration to Lucene 5 (without rebuilding old index) 2016-03-01 01:01:10 +01:00
jendib
943465a390 Closes #68: Add contributors list on documents 2016-02-21 23:43:35 +01:00
jendib
2824878065 Android: Upgrade Gradle tools 2016-02-21 15:04:26 +01:00
jendib
508a1230e9 Document updated event on file create/delete 2016-02-21 14:21:20 +01:00
jendib
0ad7ef43d5 #68: User ID available in events fired by a user 2016-02-21 14:11:17 +01:00
jendib
67171e05b9 Closes #70: User profile metadata 2016-02-20 23:49:54 +01:00
jendib
adebb7ff6d #70: User profiles UI 2016-02-17 00:28:48 +01:00
jendib
6fbcd46a76 #70: Init user profiles UI 2016-02-16 01:12:27 +01:00
jendib
ef3a592807 Closes #66: Search by creator 2016-02-15 23:09:45 +01:00
jendib
d8d01b077d Closes #69: Save and display originating user in audit log 2016-02-15 22:28:13 +01:00
jendib
831e2e60ed #65: Update README.md with Dublin Core metadata 2016-02-14 23:14:39 +01:00
jendib
2d858e6e11 #65: Limit vocabulary values to 500 characters 2016-02-14 23:11:24 +01:00
jendib
f9c3715d8d Closes #65: Type, coverage, rights metadata 2016-02-14 23:08:27 +01:00
jendib
359f5b5f49 #65: Publisher, format, source metadata 2016-02-14 22:47:49 +01:00
jendib
ed51b77b0e #65: Vocabulary admin UI 2016-02-14 21:51:46 +01:00
jendib
47082ceee9 #65: Vocabulary modification for admin only 2016-02-14 21:06:39 +01:00
jendib
98497f2a37 #65: Vocabulary resource 2016-02-14 21:00:21 +01:00
jendib
d3a74ed361 #65: PUT /vocabulary resource 2016-02-14 19:23:44 +01:00
jendib
7f2f480b25 #65: Init vocabulary resource 2016-02-14 01:58:32 +01:00
jendib
34d1422868 #65: Add subject and identifier metadata 2016-02-13 18:47:13 +01:00
jendib
509ab82745 Closes #55: Android: PDF download 2016-02-11 23:29:52 +01:00
jendib
7f325e3eb5 Android: EventBus 3 2016-02-09 22:44:24 +01:00
jendib
e23ca4b8c1 #63: Android: Null check for description in edit activity 2016-02-06 18:04:33 +01:00
jendib
a0f309c957 Upgrade libraries 2016-01-29 01:55:59 +01:00
jendib
0db4f1643d Bootstrap 3.3.6 2016-01-29 00:28:01 +01:00
jendib
cfa5888be9 #57: Android: Remove android-async-http for OkHttp 2016-01-26 00:57:48 +01:00
jendib
3172a5f216 Closes #59: Use TwelveMonkeys' ImageIO plugin for JPEG 2016-01-24 15:44:40 +01:00
jendib
456fc5b991 #57: Android: Migrate document resource to OkHttp
Closes #58: Android: OkHttpClient and cache as singleton
2016-01-23 23:20:09 +01:00
jendib
d9509474b0 #57: Android: Migrate GET /document/list to OkHttp 2016-01-21 23:48:40 +01:00
jendib
e7a289ffb5 Android: switch from AQuery to Picasso (+OkHttp) 2016-01-16 22:06:48 +01:00
jendib
b9a4f0f1e0 #55: Android: Export PDF dialog 2016-01-14 00:19:31 +01:00
jendib
0f4e5a8f6d Build against API 23 2016-01-13 23:27:39 +01:00
jendib
83e1191a8a #55: Export document in PDF (Share UI) 2016-01-01 21:38:25 +01:00
jendib
2c791f5123 #55: Export document in PDF (REST resource + export options UI) 2016-01-01 01:56:54 +01:00
jendib
25a17ae2da #55: Export document in PDF (UI) 2015-12-20 16:52:39 +01:00
jendib
0591f8a39f #55: Refactoring 2015-12-20 02:23:35 +01:00
jendib
eb61b06784 Android: update Gradle plugin 2015-12-20 02:12:59 +01:00
jendib
0d1a4ec7ea #55: Export document in PDF (utilities) 2015-12-13 22:29:23 +01:00
jendib
5f82752416 Quota updates are not polluting the audit log anymore 2015-12-12 01:56:54 +01:00
Benjamin Gamard
332de409b8 Update README.md 2015-12-11 22:28:46 +01:00
jendib
24d8784e1b Fix Junit for Unix systems 2015-12-11 22:22:21 +01:00
jendib
7708f61343 Closes #53: Build thumbnails for DOCX and ODT files 2015-12-11 22:00:44 +01:00
jendib
1a37d97a61 #53: Handle and extract text content from DOCX and ODT files 2015-12-07 23:53:30 +01:00
jendib
046984a447 Closes #51: File sizes displayed in kB or MB 2015-12-05 20:00:51 +01:00
jendib
5f516047bd #48: Soft delete before hard delete 2015-12-01 01:16:57 +01:00
jendib
e930ce4d47 Closes #48: Delete linked data properly + batch to clean orphan data 2015-12-01 00:32:57 +01:00
jendib
3dbdf88124 Closes #49: T_FILE.FIL_IDUSER_C non nullable 2015-11-30 01:02:54 +01:00
jendib
d428ce162b Merge branch 'master' of https://github.com/sismics/docs.git 2015-11-30 00:29:02 +01:00
jendib
bc323e9945 #41: Don't update deleted user quota in batch 2015-11-30 00:28:51 +01:00
Benjamin Gamard
ef69feeae7 Update README.md 2015-11-30 00:12:29 +01:00
jendib
e36143b61c Closes #41: File upload error handling + used storage updated 2015-11-30 00:08:47 +01:00
jendib
aa97253ec7 #41: Batch to rebuild quota storage + UI: show and edit quota 2015-11-29 23:14:33 +01:00
jendib
0fab0e4fc0 RAM Lucene storage for Junit + Surefire 2.18.1 forking mode 2015-11-29 20:22:24 +01:00
jendib
90a4949d76 #41: Quota increase/decrease when file is added/delete
+ java.nio-ization
2015-11-29 19:42:49 +01:00
jendib
1466fb4d6c maven central -> jcenter 2015-11-29 01:53:16 +01:00
jendib
d41172abb6 Cleanup 2015-11-29 01:53:03 +01:00
jendib
24ca81e91c #41: Storage quota editable only by admin role 2015-11-24 00:31:04 +01:00
jendib
1cae964c09 #41: DB: Storage quota and current usage, accessible from /user 2015-11-24 00:30:01 +01:00
jendib
dd671795e6 Android Studio 2 + small colors 2015-11-23 20:32:32 +01:00
jendib
2948c0c860 Update README.md 2015-11-23 00:01:39 +01:00
jendib
978fbf2cf9 Closes #45: Android: Delete comments 2015-11-22 23:59:21 +01:00
jendib
60ee000b6c #45: Android: Add comments 2015-11-22 20:32:26 +01:00
jendib
634ab7ec38 #45: Android: Show comments 2015-11-22 13:31:23 +01:00
jendib
7e5aa9aecf Closes #44: Comments visible from share app
+ metadata-complete="true" in web.xml to skip annotations scanning
(second try with Jetty 9)
2015-11-21 20:31:21 +01:00
jendib
1c7381376c Fix links to quick uploaded files from audit log 2015-11-21 17:50:10 +01:00
jendib
c7ce42fb3f Fix: handle deleted tag links in documents search 2015-11-19 00:10:04 +01:00
jendib
fc3a8bb4ae Closes #42: Gravatar images in comments 2015-11-19 00:05:04 +01:00
jendib
82b39586f0 Closes #32 : Display comments 2015-11-18 01:13:57 +01:00
jendib
c365c6f6e0 #32 : Comments layout
+ fix file viewer navigation
2015-11-17 02:48:07 +01:00
jendib
9afd52108b Merge branch 'master' of https://github.com/sismics/docs.git 2015-11-16 02:23:00 +01:00
jendib
97252bb5da #32: Comments system (server side) 2015-11-16 02:22:51 +01:00
Benjamin Gamard
7eeaeb01a0 Update README.md 2015-11-13 00:46:00 +01:00
jendib
b3e44b84d2 Closes #35: Android: Tag depth shown in tags tree 2015-11-03 00:04:09 +01:00
jendib
af23cd4948 Parent tag in GET /tag/stats 2015-11-02 23:54:07 +01:00
jendib
dc05ca0484 No wrap in tag tree 2015-11-02 22:19:58 +01:00
jendib
f94e069792 Closes #36: Android: Group ACLs by name 2015-11-02 22:12:26 +01:00
jendib
cd32f452e9 Closes #38: Handle JBIG2 images in PDF 2015-11-01 18:10:16 +01:00
jendib
66cb7333c1 Closes #33: subviews for /document/view/id 2015-11-01 14:48:07 +01:00
jendib
08633a993d Closes #34: nothing displayed if no description 2015-11-01 13:30:46 +01:00
jendib
2c782a23d8 Closes #37: Search terms in URL
+ empty tag tree
+ transitionTo -> go
+ audit log message can be empty
2015-09-22 01:34:01 +02:00
jendib
c7b7527183 Closes #23: Tag tree search 2015-09-15 23:03:42 +02:00
jendib
80bd11b44e #23: Edit tag parent 2015-09-15 00:14:13 +02:00
jendib
99a596b2e1 Merge branch 'master' of https://github.com/sismics/docs.git 2015-09-13 23:54:14 +02:00
jendib
cfde218d32 #23: Tag tree (server) 2015-09-13 23:54:06 +02:00
Benjamin Gamard
d8cefddebd Update README.md 2015-09-12 21:31:51 +02:00
jendib
50c7066f88 user agent and ip are nullable 2015-09-08 22:25:30 +02:00
jendib
a95dcf488d Closes #30: Delete locale & theme concept 2015-09-07 23:49:12 +02:00
jendib
0fe51d355c Closes #29: Upgrade to Jersey 2 2015-09-07 21:51:13 +02:00
jendib
97694d5d59 Closes #26: Cleanup Maven dependencies 2015-09-06 15:21:20 +02:00
jendib
e72fe3683c #4: Upgrade to unrelease PDFBox 2 2015-09-05 23:12:01 +02:00
jendib
44c10b60cd File update log is useless 2015-09-05 20:06:21 +02:00
jendib
467d14bacb Closes #24: Change to H2 database + indexes tweaks + queries tweaks
Tested up to 100k documents
2015-09-05 12:36:01 +02:00
jendib
6d73554967 #24: High performance is not going to happen on HSQLDB 2015-09-02 01:12:33 +02:00
jendib
36b5bf3bb2 Merge branch 'master' of https://github.com/sismics/docs.git 2015-08-31 22:53:44 +02:00
jendib
d14db1d3fb #24: Quick & dirty stress tester (slow at 60k docs with mem db) 2015-08-31 22:53:33 +02:00
jendib
9c97ab14f8 Catch all Tesseract related errors 2015-08-29 01:20:06 +02:00
jendib
6558ff7e05 tabs -> spaces 2015-08-29 00:14:47 +02:00
jendib
86473d5639 Merge branch 'master' of https://github.com/sismics/docs 2015-08-29 00:12:40 +02:00
jendib
374310d13c Init stress app 2015-08-29 00:12:15 +02:00
Benjamin Gamard
4396ef83a3 Update README.md 2015-08-28 01:32:29 +02:00
Benjamin Gamard
6b9ef4ab31 Update README.md 2015-08-28 01:18:18 +02:00
377 changed files with 25807 additions and 8138 deletions

2
.gitignore vendored
View File

@@ -5,7 +5,7 @@
/*/gen
/*/target
/*/build
/*/*.iml
/out
/.idea
/.project
*.iml

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
sudo: required
dist: trusty
language: java
before_install:
- sudo apt-get -qq update
- sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
- sudo apt-get -y -q install haveged && sudo service haveged start
env:
global:
- TESSDATA_PREFIX=/usr/share/tesseract-ocr
- LC_NUMERIC=C

View File

@@ -1,4 +1,4 @@
Sismics Docs
Sismics Docs [![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
============
_Web interface_
@@ -7,7 +7,7 @@ _Web interface_
_Android application_
![Android documents list](http://sismics.com/docs/android1.png) ![Android navigation](http://sismics.com/docs/android2.png) ![Android document details](http://sismics.com/docs/android3.png)
![Android documents list](http://sismics.com/docs/android1.png) ![Android navigation](http://sismics.com/docs/android2.png) ![Android document details](http://sismics.com/docs/android3.png) ![Android document actions](http://sismics.com/docs/android4.png)
What is Docs?
---------------
@@ -21,15 +21,21 @@ Features
- Responsive user interface
- Optical character recognition
- Support image and PDF files
- Support image, PDF, ODT and DOCX files
- Flexible search engine
- Full text search in image and PDF
- SHA-256 encryption
- Tag system
- Multi-users ACL system
- Full text search in all supported files
- All [Dublin Core](http://dublincore.org/) metadata
- 256-bit AES encryption of stored files
- Tag system with nesting
- User/group permission system
- Hierarchical groups
- Audit log
- Comments
- Storage quota per user
- Document sharing by URL
- RESTful Web API
- Modern Android client
- Fully featured Android client
- Tested to 100k documents
Download
--------
@@ -44,7 +50,6 @@ Prerequisites: JDK 7 with JCE, Maven 3, Tesseract 3.02
Docs is organized in several Maven modules:
- docs-parent
- docs-core
- docs-web
- docs-web-common
@@ -54,9 +59,8 @@ or download the sources from GitHub.
#### Launch the build
From the `docs-parent` directory:
From the root directory:
mvn -Pinit validate -N
mvn clean -DskipTests install
#### Run a stand-alone version
@@ -71,7 +75,7 @@ From the `docs-web` directory:
mvn -Pprod -DskipTests clean install
You will get your deployable WAR in the `target` directory.
You will get your deployable WAR in the `docs-web/target` directory.
License
-------

View File

@@ -1,121 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="docs-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<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>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
<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/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" 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/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" 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/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.0.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/res" />
<excludeFolder url="file://$MODULE_DIR$/build/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/source" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="appcompat-v7-22.1.1" level="project" />
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" />
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
<orderEntry type="library" exported="" name="eventbus-2.4.0" level="project" />
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.1.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.1.1" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-22.0.0" level="project" />
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
</component>
</module>

View File

@@ -1,24 +1,24 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.android.tools.build:gradle:2.1.0-beta1'
}
}
apply plugin: 'com.android.application'
repositories {
mavenCentral()
jcenter()
}
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
compileSdkVersion 23
buildToolsVersion '23.0.3'
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
@@ -50,10 +50,13 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.android.support:recyclerview-v7:22.0.0'
compile 'com.loopj.android:android-async-http:1.4.6'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:recyclerview-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'de.greenrobot:eventbus:2.4.0'
compile 'com.shamanland:fab:0.0.6'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.1.1'
compile "com.squareup.okhttp3:okhttp-urlconnection:3.1.1"
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sismics.docs" >
package="com.sismics.docs"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -47,6 +48,16 @@
android:name=".activity.DocumentEditActivity"
android:label="@string/new_document">
</activity>
<activity
android:name=".activity.AuditLogActivity"
android:label="@string/latest_activity">
</activity>
<activity
android:name=".activity.UserProfileActivity">
</activity>
<activity
android:name=".activity.GroupProfileActivity">
</activity>
<activity
android:name=".activity.SettingsActivity"
android:label="@string/settings">

View File

@@ -2,7 +2,6 @@ package com.sismics.docs;
import android.app.Application;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.util.PreferenceUtil;
@@ -20,14 +19,8 @@ public class MainApplication extends Application {
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
// TODO google docs app: right drawer with all actions, with acls, with deep metadatas
// TODO Provide documents to intent action get content
super.onCreate();
}
@Override
public void onLowMemory() {
BitmapAjaxCallback.clearCache();
}
}

View File

@@ -0,0 +1,121 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ProgressBar;
import com.sismics.docs.R;
import com.sismics.docs.adapter.AuditLogListAdapter;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.AuditLogResource;
import org.json.JSONObject;
/**
* Audit log activity.
*
* @author bgamard.
*/
public class AuditLogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input document ID (optional)
final String documentId = getIntent().getStringExtra("documentId");
// Setup the activity
setContentView(R.layout.auditlog_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Configure the swipe refresh layout
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshView(documentId);
}
});
// Navigate to user profile on click
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
auditLogListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (auditLogListView.getAdapter() == null) {
return;
}
AuditLogListAdapter adapter = (AuditLogListAdapter) auditLogListView.getAdapter();
String username = adapter.getItem(position).optString("username");
Intent intent = new Intent(AuditLogActivity.this, UserProfileActivity.class);
intent.putExtra("username", username);
startActivity(intent);
}
});
// Get audit log list
refreshView(documentId);
}
/**
* Refresh the view.
*/
private void refreshView(String documentId) {
final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
progressBar.setVisibility(View.VISIBLE);
auditLogListView.setVisibility(View.GONE);
AuditLogResource.list(this, documentId, new HttpCallback() {
@Override
public void onSuccess(JSONObject response) {
auditLogListView.setAdapter(new AuditLogListAdapter(response.optJSONArray("logs")));
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
auditLogListView.setVisibility(View.VISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,7 +1,6 @@
package com.sismics.docs.activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
@@ -17,7 +16,7 @@ import com.sismics.docs.adapter.LanguageAdapter;
import com.sismics.docs.adapter.TagAutoCompleteAdapter;
import com.sismics.docs.event.DocumentAddEvent;
import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.ui.form.Validator;
import com.sismics.docs.ui.form.validator.Required;
@@ -25,7 +24,7 @@ import com.sismics.docs.ui.view.DatePickerView;
import com.sismics.docs.ui.view.TagsCompleteTextView;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,8 +35,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.greenrobot.event.EventBus;
/**
* Document edition activity.
*
@@ -124,7 +121,7 @@ public class DocumentEditActivity extends AppCompatActivity {
} else {
setTitle(R.string.edit_document);
titleEditText.setText(document.optString("title"));
descriptionEditText.setText(document.optString("description"));
descriptionEditText.setText(document.isNull("description") ? "" : document.optString("description"));
datePickerView.setDate(new Date(document.optLong("create_date")));
languageSpinner.setSelection(languageAdapter.getItemPosition(document.optString("language")));
JSONArray documentTags = document.optJSONArray("tags");
@@ -165,18 +162,12 @@ public class DocumentEditActivity extends AppCompatActivity {
// Cancellable progress dialog
final ProgressDialog progressDialog = ProgressDialog.show(this,
getString(R.string.please_wait),
getString(R.string.document_editing_message), true, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
DocumentResource.cancel(DocumentEditActivity.this);
}
});
getString(R.string.document_editing_message), true, true);
// Server callback
JsonHttpResponseHandler callback = new JsonHttpResponseHandler() {
HttpCallback callback = new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
// Build a fake document JSON to update the UI
final JSONObject outputDoc = new JSONObject();
try {
@@ -211,7 +202,7 @@ public class DocumentEditActivity extends AppCompatActivity {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentEditActivity.this, R.string.error_editing_document, Toast.LENGTH_LONG).show();
}

View File

@@ -1,16 +1,13 @@
package com.sismics.docs.activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.DialogFragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewPager;
@@ -19,11 +16,15 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -31,22 +32,30 @@ import android.widget.Toast;
import com.sismics.docs.R;
import com.sismics.docs.adapter.AclListAdapter;
import com.sismics.docs.adapter.CommentListAdapter;
import com.sismics.docs.adapter.FilePagerAdapter;
import com.sismics.docs.event.CommentAddEvent;
import com.sismics.docs.event.CommentDeleteEvent;
import com.sismics.docs.event.DocumentDeleteEvent;
import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.event.DocumentFullscreenEvent;
import com.sismics.docs.event.FileAddEvent;
import com.sismics.docs.event.FileDeleteEvent;
import com.sismics.docs.fragment.DocExportPdfFragment;
import com.sismics.docs.fragment.DocShareFragment;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.CommentResource;
import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.resource.FileResource;
import com.sismics.docs.service.FileUploadService;
import com.sismics.docs.util.NetworkUtil;
import com.sismics.docs.util.PreferenceUtil;
import com.sismics.docs.util.TagUtil;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -55,8 +64,6 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import de.greenrobot.event.EventBus;
/**
* Document activity.
*
@@ -83,6 +90,11 @@ public class DocumentViewActivity extends AppCompatActivity {
*/
private FilePagerAdapter filePagerAdapter;
/**
* Comment list adapter.
*/
private CommentListAdapter commentListAdapter;
/**
* Document displayed.
*/
@@ -142,7 +154,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param document Document in JSON format
*/
private void refreshDocument(JSONObject document) {
private void refreshDocument(final JSONObject document) {
this.document = document;
String title = document.optString("title");
@@ -168,7 +180,7 @@ public class DocumentViewActivity extends AppCompatActivity {
createdDateTextView.setText(date);
TextView descriptionTextView = (TextView) findViewById(R.id.descriptionTextView);
if (description == null || description.isEmpty()) {
if (description.isEmpty() || document.isNull("description")) {
descriptionTextView.setVisibility(View.GONE);
} else {
descriptionTextView.setVisibility(View.VISIBLE);
@@ -231,20 +243,75 @@ public class DocumentViewActivity extends AppCompatActivity {
}
});
// Action export PDF
button = (Button) findViewById(R.id.actionExportPdf);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment dialog = DocExportPdfFragment.newInstance(
document.optString("id"), document.optString("title"));
dialog.show(getSupportFragmentManager(), "DocExportPdfFragment");
}
});
// Action share
button = (Button) findViewById(R.id.actionSharing);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment dialog = DocShareFragment.newInstance(DocumentViewActivity.this.document.optString("id"));
DialogFragment dialog = DocShareFragment.newInstance(document.optString("id"));
dialog.show(getSupportFragmentManager(), "DocShareFragment");
}
});
// Action audit log
button = (Button) findViewById(R.id.actionAuditLog);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(DocumentViewActivity.this, AuditLogActivity.class);
intent.putExtra("documentId", document.optString("id"));
startActivity(intent);
}
});
// Button add a comment
ImageButton imageButton = (ImageButton) findViewById(R.id.addCommentBtn);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final EditText commentEditText = (EditText) findViewById(R.id.commentEditText);
if (commentEditText.getText().length() == 0) {
// No content for the new comment
return;
}
Toast.makeText(DocumentViewActivity.this, R.string.adding_comment, Toast.LENGTH_LONG).show();
CommentResource.add(DocumentViewActivity.this,
DocumentViewActivity.this.document.optString("id"),
commentEditText.getText().toString(),
new HttpCallback() {
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new CommentAddEvent(response));
commentEditText.setText("");
}
@Override
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentViewActivity.this, R.string.comment_add_failure, Toast.LENGTH_LONG).show();
}
});
}
});
// Grab the comments
updateComments();
// Grab the attached files
updateFiles();
// Grab the full document (used for ACLs and writable status)
// Grab the full document (used for ACLs, remaining metadata and writable status)
updateDocument();
}
@@ -268,6 +335,15 @@ public class DocumentViewActivity extends AppCompatActivity {
}
return true;
case R.id.comments:
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
drawerLayout.openDrawer(GravityCompat.START);
}
return true;
case R.id.download_file:
downloadCurrentFile();
return true;
@@ -299,11 +375,11 @@ public class DocumentViewActivity extends AppCompatActivity {
int position = fileViewPager.getCurrentItem();
if (mimeType == null || !mimeType.contains("/")) return;
String ext = mimeType.split("/")[1];
String fileName = getTitle() + "-" + position + "." + ext;
String fileName = document.optString("title") + "-" + position + "." + ext;
// Download the file
String fileUrl = PreferenceUtil.getServerUrl(this) + "/api/file/" + file.optString("id") + "/data";
downloadFile(fileUrl, fileName, getTitle().toString(), getString(R.string.downloading_file, position + 1));
NetworkUtil.downloadFile(this, fileUrl, fileName, document.optString("title"), getString(R.string.download_file_title));
}
private void deleteCurrentFile() {
@@ -326,24 +402,18 @@ public class DocumentViewActivity extends AppCompatActivity {
// Show a progress dialog while deleting
final ProgressDialog progressDialog = ProgressDialog.show(DocumentViewActivity.this,
getString(R.string.please_wait),
getString(R.string.file_deleting_message), true, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
FileResource.cancel(DocumentViewActivity.this);
}
});
getString(R.string.file_deleting_message), true, true);
// Actual delete server call
final String fileId = file.optString("id");
FileResource.delete(DocumentViewActivity.this, fileId, new JsonHttpResponseHandler() {
FileResource.delete(DocumentViewActivity.this, fileId, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new FileDeleteEvent(fileId));
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentViewActivity.this, R.string.file_delete_failure, Toast.LENGTH_LONG).show();
}
@@ -368,28 +438,8 @@ public class DocumentViewActivity extends AppCompatActivity {
private void downloadZip() {
if (document == null) return;
String url = PreferenceUtil.getServerUrl(this) + "/api/file/zip?id=" + document.optString("id");
String fileName = getTitle() + ".zip";
downloadFile(url, fileName, getTitle().toString(), getString(R.string.downloading_document));
}
/**
* Download a file using Android download manager.
*
* @param url URL to download
* @param fileName Destination file name
* @param title Notification title
* @param description Notification description
*/
private void downloadFile(String url, String fileName, String title, String description) {
String authToken = PreferenceUtil.getAuthToken(this);
DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
request.addRequestHeader("Cookie", "auth_token=" + authToken);
request.setTitle(title);
request.setDescription(description);
downloadManager.enqueue(request);
String fileName = document.optString("title") + ".zip";
NetworkUtil.downloadFile(this, url, fileName, document.optString("title"), getString(R.string.download_document_title));
}
/**
@@ -411,24 +461,18 @@ public class DocumentViewActivity extends AppCompatActivity {
// Show a progress dialog while deleting
final ProgressDialog progressDialog = ProgressDialog.show(DocumentViewActivity.this,
getString(R.string.please_wait),
getString(R.string.document_deleting_message), true, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
DocumentResource.cancel(DocumentViewActivity.this);
}
});
getString(R.string.document_deleting_message), true, true);
// Actual delete server call
final String documentId = document.optString("id");
DocumentResource.delete(DocumentViewActivity.this, documentId, new JsonHttpResponseHandler() {
DocumentResource.delete(DocumentViewActivity.this, documentId, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new DocumentDeleteEvent(documentId));
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentViewActivity.this, R.string.document_delete_failure, Toast.LENGTH_LONG).show();
}
@@ -453,6 +497,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param event Document fullscreen event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentFullscreenEvent event) {
findViewById(R.id.detailLayout).setVisibility(event.isFullscreen() ? View.GONE : View.VISIBLE);
}
@@ -462,6 +507,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param event Document edit event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentEditEvent event) {
if (document == null) return;
if (event.getDocument().optString("id").equals(document.optString("id"))) {
@@ -475,6 +521,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param event Document delete event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentDeleteEvent event) {
if (document == null) return;
if (event.getDocumentId().equals(document.optString("id"))) {
@@ -488,6 +535,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param event File delete event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FileDeleteEvent event) {
if (filePagerAdapter == null) return;
filePagerAdapter.remove(event.getFileId());
@@ -500,6 +548,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param event File add event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(FileAddEvent event) {
if (document == null) return;
if (document.optString("id").equals(event.getDocumentId())) {
@@ -507,6 +556,38 @@ public class DocumentViewActivity extends AppCompatActivity {
}
}
/**
* A comment add event has been fired.
*
* @param event Comment add event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(CommentAddEvent event) {
if (commentListAdapter == null) return;
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
ListView listView = (ListView) findViewById(R.id.commentListView);
emptyView.setVisibility(View.GONE);
listView.setVisibility(View.VISIBLE);
commentListAdapter.add(event.getComment());
}
/**
* A comment delete event has been fired.
*
* @param event Comment add event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(CommentDeleteEvent event) {
if (commentListAdapter == null) return;
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
ListView listView = (ListView) findViewById(R.id.commentListView);
commentListAdapter.remove(event.getCommentId());
if (commentListAdapter.getCount() == 0) {
emptyView.setVisibility(View.VISIBLE);
listView.setVisibility(View.GONE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (document == null) return;
@@ -550,9 +631,9 @@ public class DocumentViewActivity extends AppCompatActivity {
// Silently get the document to know if it is writable by the current user
// If this call fails or is slow and the document is read-only,
// write actions will be allowed and will fail
DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() {
DocumentResource.get(this, document.optString("id"), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
document = response;
boolean writable = document.optBoolean("writable");
@@ -560,6 +641,7 @@ public class DocumentViewActivity extends AppCompatActivity {
menu.findItem(R.id.delete_file).setVisible(writable);
}
// Action only available if the document is writable
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
@@ -567,7 +649,119 @@ public class DocumentViewActivity extends AppCompatActivity {
// ACLs
ListView aclListView = (ListView) findViewById(R.id.aclListView);
aclListView.setAdapter(new AclListAdapter(document.optJSONArray("acls")));
final AclListAdapter aclListAdapter = new AclListAdapter(document.optJSONArray("acls"));
aclListView.setAdapter(aclListAdapter);
aclListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
AclListAdapter.AclItem acl = aclListAdapter.getItem(position);
if (acl.getType().equals("USER")) {
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
intent.putExtra("username", acl.getName());
startActivity(intent);
} else if (acl.getType().equals("GROUP")) {
Intent intent = new Intent(DocumentViewActivity.this, GroupProfileActivity.class);
intent.putExtra("name", acl.getName());
startActivity(intent);
}
}
});
// Remaining metadata
TextView creatorTextView = (TextView) findViewById(R.id.creatorTextView);
final String creator = document.optString("creator");
creatorTextView.setText(creator);
creatorTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
intent.putExtra("username", creator);
startActivity(intent);
}
});
}
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
switch (view.getId()) {
case R.id.commentListView:
if (commentListAdapter == null || document == null) return;
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
JSONObject comment = commentListAdapter.getItem(info.position);
boolean writable = document.optBoolean("writable");
String creator = comment.optString("creator");
String username = ApplicationContext.getInstance().getUserInfo().optString("username");
if (writable || creator.equals(username)) {
menu.add(Menu.NONE, 0, 0, getString(R.string.comment_delete));
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
// Use real ids if more than one item someday
if (item.getItemId() == 0) {
// Delete a comment
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
if (commentListAdapter == null) return false;
JSONObject comment = commentListAdapter.getItem(info.position);
final String commentId = comment.optString("id");
Toast.makeText(DocumentViewActivity.this, R.string.deleting_comment, Toast.LENGTH_LONG).show();
CommentResource.remove(DocumentViewActivity.this, commentId, new HttpCallback() {
@Override
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new CommentDeleteEvent(commentId));
}
@Override
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentViewActivity.this, R.string.error_deleting_comment, Toast.LENGTH_LONG).show();
}
});
return true;
}
return false;
}
/**
* Refresh comments list.
*/
private void updateComments() {
if (document == null) return;
final View progressBar = findViewById(R.id.commentProgressView);
final TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
final ListView listView = (ListView) findViewById(R.id.commentListView);
progressBar.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
listView.setVisibility(View.GONE);
registerForContextMenu(listView);
CommentResource.list(this, document.optString("id"), new HttpCallback() {
@Override
public void onSuccess(JSONObject response) {
JSONArray comments = response.optJSONArray("comments");
commentListAdapter = new CommentListAdapter(DocumentViewActivity.this, comments);
listView.setAdapter(commentListAdapter);
listView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
if (comments.length() == 0) {
listView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
}
@Override
public void onFailure(JSONObject json, Exception e) {
emptyView.setText(R.string.error_loading_comments);
progressBar.setVisibility(View.GONE);
listView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
});
}
@@ -586,9 +780,9 @@ public class DocumentViewActivity extends AppCompatActivity {
progressBar.setVisibility(View.VISIBLE);
filesEmptyView.setVisibility(View.GONE);
FileResource.list(this, document.optString("id"), new JsonHttpResponseHandler() {
FileResource.list(this, document.optString("id"), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
JSONArray files = response.optJSONArray("files");
filePagerAdapter = new FilePagerAdapter(DocumentViewActivity.this, files);
fileViewPager.setAdapter(filePagerAdapter);
@@ -598,7 +792,7 @@ public class DocumentViewActivity extends AppCompatActivity {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
filesEmptyView.setText(R.string.error_loading_files);
progressBar.setVisibility(View.GONE);
filesEmptyView.setVisibility(View.VISIBLE);

View File

@@ -0,0 +1,92 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.UserResource;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Group profile activity.
*
* @author bgamard.
*/
public class GroupProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input name
final String name = getIntent().getStringExtra("name");
if (name == null) {
finish();
return;
}
// Setup the activity
setTitle(name);
setContentView(R.layout.groupprofile_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Get the group and populate the view
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final View layoutView = findViewById(R.id.layout);
progressBar.setVisibility(View.VISIBLE);
layoutView.setVisibility(View.GONE);
UserResource.get(this, name, new HttpCallback() {
@Override
public void onSuccess(JSONObject json) {
TextView membersTextView = (TextView) findViewById(R.id.membersTextView);
JSONArray members = json.optJSONArray("members");
String output = "";
for (int i = 0; i < members.length(); i++) {
output += members.optString(i) + "; ";
}
membersTextView.setText(output);
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
layoutView.setVisibility(View.VISIBLE);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -10,11 +10,11 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.androidquery.AQuery;
import com.sismics.docs.R;
import com.sismics.docs.listener.CallbackListener;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.UserResource;
import com.sismics.docs.ui.form.Validator;
@@ -22,7 +22,6 @@ import com.sismics.docs.ui.form.validator.Required;
import com.sismics.docs.util.DialogUtil;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject;
/**
@@ -31,7 +30,6 @@ import org.json.JSONObject;
* @author bgamard
*/
public class LoginActivity extends AppCompatActivity {
/**
* User interface.
*/
@@ -43,18 +41,17 @@ public class LoginActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
AQuery aq = new AQuery(this);
aq.id(R.id.loginExplain)
.text(Html.fromHtml(getString(R.string.login_explain)))
.getTextView()
.setMovementMethod(LinkMovementMethod.getInstance());
TextView loginExplainTextView = (TextView) findViewById(R.id.loginExplain);
loginExplainTextView.setText(Html.fromHtml(getString(R.string.login_explain)));
loginExplainTextView.setMovementMethod(LinkMovementMethod.getInstance());
final EditText txtServer = aq.id(R.id.txtServer).getEditText();
final EditText txtUsername = aq.id(R.id.txtUsername).getEditText();
final EditText txtPassword = aq.id(R.id.txtPassword).getEditText();
final Button btnConnect = aq.id(R.id.btnConnect).getButton();
loginForm = aq.id(R.id.loginForm).getView();
progressBar = aq.id(R.id.progressBar).getView();
final EditText txtServer = (EditText) findViewById(R.id.txtServer);
final EditText txtUsername = (EditText) findViewById(R.id.txtUsername);
final EditText txtPassword = (EditText) findViewById(R.id.txtPassword);
final EditText txtValidationCode = (EditText) findViewById(R.id.txtValidationCode);
final Button btnConnect = (Button) findViewById(R.id.btnConnect);
loginForm = findViewById(R.id.loginForm);
progressBar = findViewById(R.id.progressBar);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
@@ -91,9 +88,11 @@ public class LoginActivity extends AppCompatActivity {
PreferenceUtil.setServerUrl(LoginActivity.this, txtServer.getText().toString());
try {
UserResource.login(getApplicationContext(), txtUsername.getText().toString(), txtPassword.getText().toString(), new JsonHttpResponseHandler() {
UserResource.login(getApplicationContext(), txtUsername.getText().toString(),
txtPassword.getText().toString(), txtValidationCode.getText().toString(),
new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject json) {
public void onSuccess(JSONObject json) {
// Empty previous user caches
PreferenceUtil.resetUserCache(getApplicationContext());
@@ -109,12 +108,16 @@ public class LoginActivity extends AppCompatActivity {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
loginForm.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
if (responseBytes != null && new String(responseBytes).contains("\"ForbiddenError\"")) {
if (json != null && json.optString("type").equals("ForbiddenError")) {
DialogUtil.showOkDialog(LoginActivity.this, R.string.login_fail_title, R.string.login_fail);
} else if (json != null && json.optString("type").equals("ValidationCodeRequired")) {
txtValidationCode.setVisibility(View.VISIBLE);
validator.addValidable(txtValidationCode, new Required());
validator.validate();
} else {
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
}
@@ -151,9 +154,9 @@ public class LoginActivity extends AppCompatActivity {
finish();
} else {
// Trying to get user data
UserResource.info(getApplicationContext(), new JsonHttpResponseHandler() {
UserResource.info(getApplicationContext(), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, final JSONObject json) {
public void onSuccess(final JSONObject json) {
if (json.optBoolean("anonymous", true)) {
loginForm.setVisibility(View.VISIBLE);
return;
@@ -169,7 +172,7 @@ public class LoginActivity extends AppCompatActivity {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
loginForm.setVisibility(View.VISIBLE);
}

View File

@@ -18,24 +18,23 @@ import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R;
import com.sismics.docs.adapter.TagListAdapter;
import com.sismics.docs.event.AdvancedSearchEvent;
import com.sismics.docs.event.SearchEvent;
import com.sismics.docs.fragment.SearchFragment;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.provider.RecentSuggestionsProvider;
import com.sismics.docs.resource.TagResource;
import com.sismics.docs.resource.UserResource;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONObject;
import de.greenrobot.event.EventBus;
/**
* Main activity.
*
@@ -43,7 +42,6 @@ import de.greenrobot.event.EventBus;
*/
public class MainActivity extends AppCompatActivity {
private ActionBarDrawerToggle drawerToggle;
private MenuItem searchItem;
private DrawerLayout drawerLayout;
@@ -73,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
// between the sliding drawer and the action bar app icon
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.drawer_open, R.string.drawer_close);
drawerLayout.setDrawerListener(drawerToggle);
drawerLayout.addDrawerListener(drawerToggle);
// Fill the drawer user info
JSONObject userInfo = ApplicationContext.getInstance().getUserInfo();
@@ -91,9 +89,9 @@ public class MainActivity extends AppCompatActivity {
if (cacheTags != null) {
tagListView.setAdapter(new TagListAdapter(cacheTags.optJSONArray("stats")));
}
TagResource.stats(this, new JsonHttpResponseHandler() {
TagResource.stats(this, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
PreferenceUtil.setCachedJson(MainActivity.this, PreferenceUtil.PREF_CACHED_TAGS_JSON, response);
tagListView.setAdapter(new TagListAdapter(response.optJSONArray("stats")));
tagProgressView.setVisibility(View.GONE);
@@ -101,7 +99,7 @@ public class MainActivity extends AppCompatActivity {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
tagEmptyView.setText(R.string.error_loading_tags);
tagProgressView.setVisibility(View.GONE);
tagListView.setEmptyView(tagEmptyView);
@@ -114,9 +112,9 @@ public class MainActivity extends AppCompatActivity {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TagListAdapter adapter = (TagListAdapter) tagListView.getAdapter();
if (adapter == null) return;
JSONObject tag = adapter.getItem(position);
if (tag == null) return;
searchQuery("tag:" + tag.optString("name"));
TagListAdapter.TagItem tagItem = adapter.getItem(position);
if (tagItem == null) return;
searchQuery("tag:" + tagItem.getName());
}
});
@@ -138,6 +136,15 @@ public class MainActivity extends AppCompatActivity {
}
});
// Click on Latest activity
View auditLogLayout = findViewById(R.id.auditLogLayout);
auditLogLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, AuditLogActivity.class));
}
});
handleIntent(getIntent());
EventBus.getDefault().register(this);
@@ -147,7 +154,7 @@ public class MainActivity extends AppCompatActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.logout:
UserResource.logout(getApplicationContext(), new JsonHttpResponseHandler() {
UserResource.logout(getApplicationContext(), new HttpCallback() {
@Override
public void onFinish() {
// Force logout in all cases, so the user is not stuck in case of network error
@@ -267,6 +274,7 @@ public class MainActivity extends AppCompatActivity {
*
* @param event Advanced search event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(AdvancedSearchEvent event) {
searchQuery(event.getQuery());
}
@@ -274,10 +282,6 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this);
if(isTaskRoot()) {
int cacheSizeMb = PreferenceUtil.getIntegerPreference(this, PreferenceUtil.PREF_CACHE_SIZE, 10);
AQUtility.cleanCacheAsync(this, cacheSizeMb * 1000000, cacheSizeMb * 1000000);
}
super.onDestroy();
}
}

View File

@@ -12,7 +12,6 @@ import com.sismics.docs.fragment.SettingsFragment;
* @author bgamard.
*/
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@@ -0,0 +1,91 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.UserResource;
import org.json.JSONObject;
/**
* User profile activity.
*
* @author bgamard.
*/
public class UserProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input username
final String username = getIntent().getStringExtra("username");
if (username == null) {
finish();
return;
}
// Setup the activity
setTitle(username);
setContentView(R.layout.userprofile_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Get the user and populate the view
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final View layoutView = findViewById(R.id.layout);
progressBar.setVisibility(View.VISIBLE);
layoutView.setVisibility(View.GONE);
UserResource.get(this, username, new HttpCallback() {
@Override
public void onSuccess(JSONObject json) {
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
emailTextView.setText(json.optString("email"));
TextView quotaTextView = (TextView) findViewById(R.id.quotaTextView);
quotaTextView.setText(getString(R.string.storage_display,
Math.round(json.optLong("storage_current") / 1000000),
Math.round(json.optLong("storage_quota") / 1000000)));
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
layoutView.setVisibility(View.VISIBLE);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,6 +1,7 @@
package com.sismics.docs.adapter;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -24,7 +25,7 @@ public class AclListAdapter extends BaseAdapter {
/**
* Shares.
*/
private List<JSONObject> acls;
private List<AclItem> aclItemList;
/**
* ACL list adapter.
@@ -32,28 +33,46 @@ public class AclListAdapter extends BaseAdapter {
* @param acls ACLs
*/
public AclListAdapter(JSONArray acls) {
this.acls = new ArrayList<>();
this.aclItemList = new ArrayList<>();
// Extract only share ACLs
// Group ACLs
for (int i = 0; i < acls.length(); i++) {
JSONObject acl = acls.optJSONObject(i);
this.acls.add(acl);
String type = acl.optString("type");
String name = acl.optString("name");
String perm = acl.optString("perm");
boolean found = false;
for (AclItem aclItem : aclItemList) {
if (aclItem.type.equals(type) && aclItem.name.equals(name)) {
aclItem.permList.add(perm);
found = true;
}
}
if (!found) {
AclItem aclItem = new AclItem();
aclItem.type = type;
aclItem.name = name;
aclItem.permList.add(perm);
this.aclItemList.add(aclItem);
}
}
}
@Override
public int getCount() {
return acls.size();
return aclItemList.size();
}
@Override
public JSONObject getItem(int position) {
return acls.get(position);
public AclItem getItem(int position) {
return aclItemList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).optString("id").hashCode();
return getItem(position).hashCode();
}
@Override
@@ -64,14 +83,37 @@ public class AclListAdapter extends BaseAdapter {
}
// Fill the view
final JSONObject acl = getItem(position);
final AclItem aclItem = getItem(position);
TextView typeTextView = (TextView) view.findViewById(R.id.typeTextView);
typeTextView.setText(acl.optString("type"));
typeTextView.setText(aclItem.type);
TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
nameTextView.setText(acl.optString("name"));
nameTextView.setText(aclItem.name);
TextView permTextView = (TextView) view.findViewById(R.id.permTextView);
permTextView.setText(acl.optString("perm"));
permTextView.setText(TextUtils.join(" + ", aclItem.permList));
return view;
}
/**
* An ACL item in the list.
* Permissions are grouped together.
*/
public static class AclItem {
private String type;
private String name;
private List<String> permList = new ArrayList<>();
public String getType() {
return type;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return (type + name).hashCode();
}
}
}

View File

@@ -0,0 +1,98 @@
package com.sismics.docs.adapter;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.sismics.docs.R;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Audit log list adapter.
*
* @author bgamard.
*/
public class AuditLogListAdapter extends BaseAdapter {
/**
* Shares.
*/
private List<JSONObject> logList;
/**
* Audit log list adapter.
*
* @param logs Logs
*/
public AuditLogListAdapter(JSONArray logs) {
this.logList = new ArrayList<>();
for (int i = 0; i < logs.length(); i++) {
logList.add(logs.optJSONObject(i));
}
}
@Override
public int getCount() {
return logList.size();
}
@Override
public JSONObject getItem(int position) {
return logList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
@Override
public View getView(int position, View view, final ViewGroup parent) {
if (view == null) {
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.auditlog_list_item, parent, false);
}
// Build message
final JSONObject log = getItem(position);
StringBuilder message = new StringBuilder(log.optString("class"));
switch (log.optString("type")) {
case "CREATE": message.append(" created"); break;
case "UPDATE": message.append(" updated"); break;
case "DELETE": message.append(" deleted"); break;
}
switch (log.optString("class")) {
case "Document":
case "Acl":
case "Tag":
case "User":
case "Group":
message.append(" : ");
message.append(log.optString("message"));
break;
}
// Fill the view
TextView usernameTextView = (TextView) view.findViewById(R.id.usernameTextView);
TextView messageTextView = (TextView) view.findViewById(R.id.messageTextView);
TextView dateTextView = (TextView) view.findViewById(R.id.dateTextView);
usernameTextView.setText(log.optString("username"));
messageTextView.setText(message);
String date = DateFormat.getDateFormat(parent.getContext()).format(new Date(log.optLong("create_date")));
dateTextView.setText(date);
return view;
}
}

View File

@@ -0,0 +1,115 @@
package com.sismics.docs.adapter;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.util.OkHttpUtil;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Comment list adapter.
*
* @author bgamard.
*/
public class CommentListAdapter extends BaseAdapter {
/**
* Tags.
*/
private List<JSONObject> commentList = new ArrayList<>();
/**
* Context.
*/
private Context context;
/**
* Comment list adapter.
*
* @param commentsArray Comments
*/
public CommentListAdapter(Context context, JSONArray commentsArray) {
this.context = context;
for (int i = 0; i < commentsArray.length(); i++) {
commentList.add(commentsArray.optJSONObject(i));
}
}
@Override
public int getCount() {
return commentList.size();
}
@Override
public JSONObject getItem(int position) {
return commentList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).optString("id").hashCode();
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.comment_list_item, parent, false);
}
// Fill the view
JSONObject comment = getItem(position);
TextView creatorTextView = (TextView) view.findViewById(R.id.creatorTextView);
TextView dateTextView = (TextView) view.findViewById(R.id.dateTextView);
TextView contentTextView = (TextView) view.findViewById(R.id.contentTextView);
ImageView gravatarImageView = (ImageView) view.findViewById(R.id.gravatarImageView);
creatorTextView.setText(comment.optString("creator"));
dateTextView.setText(DateFormat.getDateFormat(dateTextView.getContext()).format(new Date(comment.optLong("create_date"))));
contentTextView.setText(comment.optString("content"));
// Gravatar image
String gravatarUrl = "http://www.gravatar.com/avatar/" + comment.optString("creator_gravatar") + "?s=128d=identicon";
OkHttpUtil.picasso(context)
.load(gravatarUrl)
.into(gravatarImageView);
return view;
}
/**
* Add a new comment.
*
* @param comment Comment
*/
public void add(JSONObject comment) {
commentList.add(comment);
notifyDataSetChanged();
}
/**
* Remove a comment.
*
* @param commentId Comment ID
*/
public void remove(String commentId) {
for (JSONObject comment : commentList) {
if (comment.optString("id").equals(commentId)) {
commentList.remove(comment);
notifyDataSetChanged();
return;
}
}
}
}

View File

@@ -7,10 +7,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.androidquery.AQuery;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.R;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil;
import com.squareup.picasso.Callback;
import com.squareup.picasso.MemoryPolicy;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -30,21 +31,11 @@ public class FilePagerAdapter extends PagerAdapter {
*/
private List<JSONObject> files;
/**
* AQuery.
*/
private AQuery aq;
/**
* Context.
*/
private Context context;
/**
* Auth token used to download files.
*/
private String authToken;
/**
* File pager adapter.
*
@@ -57,8 +48,6 @@ public class FilePagerAdapter extends PagerAdapter {
files.add(filesArray.optJSONObject(i));
}
this.context = context;
this.authToken = PreferenceUtil.getAuthToken(context);
aq = new AQuery(context);
}
@Override
@@ -66,15 +55,20 @@ public class FilePagerAdapter extends PagerAdapter {
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.file_viewpager_item, container, false);
ImageViewTouch fileImageView = (ImageViewTouch) view.findViewById(R.id.fileImageView);
ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.fileProgressBar);
final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.fileProgressBar);
JSONObject file = files.get(position);
String fileUrl = PreferenceUtil.getServerUrl(context) + "/api/file/" + file.optString("id") + "/data?size=web";
aq.id(fileImageView)
.image(new BitmapAjaxCallback()
.url(fileUrl)
.progress(progressBar)
.animation(AQuery.FADE_IN_NETWORK)
.cookie("auth_token", authToken));
// Load image
OkHttpUtil.picasso(context)
.load(fileUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // Don't memory cache the images
.into(fileImageView, new Callback.EmptyCallback() {
@Override
public void onSuccess() {
progressBar.setVisibility(View.GONE);
}
});
fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
@@ -109,7 +103,7 @@ public class FilePagerAdapter extends PagerAdapter {
* @return Object
*/
public JSONObject getObjectAt(int position) {
if (files == null) {
if (files == null || position < 0 || position >= files.size()) {
return null;
}

View File

@@ -12,14 +12,13 @@ import com.sismics.docs.R;
import com.sismics.docs.event.ShareDeleteEvent;
import com.sismics.docs.event.ShareSendEvent;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import de.greenrobot.event.EventBus;
/**
* Share list adapter.
*

View File

@@ -1,9 +1,11 @@
package com.sismics.docs.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -17,9 +19,8 @@ import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
/**
* Tag list adapter.
@@ -30,7 +31,7 @@ public class TagListAdapter extends BaseAdapter {
/**
* Tags.
*/
private List<JSONObject> tags;
private List<TagItem> tagItemList = new ArrayList<>();
/**
* Tag list adapter.
@@ -38,33 +39,53 @@ public class TagListAdapter extends BaseAdapter {
* @param tagsArray Tags
*/
public TagListAdapter(JSONArray tagsArray) {
this.tags = new ArrayList<>();
List<JSONObject> tags = new ArrayList<>();
for (int i = 0; i < tagsArray.length(); i++) {
tags.add(tagsArray.optJSONObject(i));
}
// Sort tags by count desc
Collections.sort(tags, new Comparator<JSONObject>() {
@Override
public int compare(JSONObject lhs, JSONObject rhs) {
return lhs.optInt("count") < rhs.optInt("count") ? 1 : -1;
// Reorder tags by parent/child relation and compute depth
int depth = 0;
initTags(tags, JSONObject.NULL.toString(), depth);
}
/**
* Init tags model recursively.
*
* @param tags All tags from server
* @param parentId Parent ID
* @param depth Depth
*/
private void initTags(List<JSONObject> tags, String parentId, int depth) {
// Get all tags with this parent
for (JSONObject tag : tags) {
String tagParentId = tag.optString("parent");
if (tagParentId.equals(parentId)) {
TagItem tagItem = new TagItem();
tagItem.id = tag.optString("id");
tagItem.name = tag.optString("name");
tagItem.count = tag.optInt("count");
tagItem.color = tag.optString("color");
tagItem.depth = depth;
tagItemList.add(tagItem);
initTags(tags, tagItem.id, depth + 1);
}
});
}
}
@Override
public int getCount() {
return tags.size();
return tagItemList.size();
}
@Override
public JSONObject getItem(int position) {
return tags.get(position);
public TagItem getItem(int position) {
return tagItemList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).optString("id").hashCode();
return getItem(position).id.hashCode();
}
@Override
@@ -75,19 +96,41 @@ public class TagListAdapter extends BaseAdapter {
}
// Fill the view
JSONObject tag = getItem(position);
TagItem tagItem = getItem(position);
TextView tagTextView = (TextView) view.findViewById(R.id.tagTextView);
tagTextView.setText(tag.optString("name"));
tagTextView.setText(tagItem.name);
TextView tagCountTextView = (TextView) view.findViewById(R.id.tagCountTextView);
tagCountTextView.setText(tag.optString("count"));
tagCountTextView.setText(String.format(Locale.ENGLISH, "%d", tagItem.count));
// Label color filtering
ImageView labelImageView = (ImageView) view.findViewById(R.id.labelImageView);
Drawable labelDrawable = labelImageView.getDrawable().mutate();
labelDrawable.setColorFilter(Color.parseColor(tag.optString("color")), PorterDuff.Mode.MULTIPLY);
labelDrawable.setColorFilter(Color.parseColor(tagItem.color), PorterDuff.Mode.MULTIPLY);
labelImageView.setImageDrawable(labelDrawable);
labelImageView.invalidate();
// Offset according to depth
Resources resources = parent.getContext().getResources();
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) labelImageView.getLayoutParams();
layoutParams.leftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tagItem.depth * 12, resources.getDisplayMetrics());
labelImageView.setLayoutParams(layoutParams);
labelImageView.requestLayout();
return view;
}
/**
* A tag item in the tags list.
*/
public static class TagItem {
private String id;
private String name;
private int count;
private String color;
private int depth;
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,33 @@
package com.sismics.docs.event;
import org.json.JSONObject;
/**
* Comment add event.
*
* @author bgamard.
*/
public class CommentAddEvent {
/**
* Comment.
*/
private JSONObject comment;
/**
* Create a comment add event.
*
* @param comment Comment
*/
public CommentAddEvent(JSONObject comment) {
this.comment = comment;
}
/**
* Getter of comment.
*
* @return comment
*/
public JSONObject getComment() {
return comment;
}
}

View File

@@ -0,0 +1,31 @@
package com.sismics.docs.event;
/**
* Comment delete event.
*
* @author bgamard.
*/
public class CommentDeleteEvent {
/**
* Comment ID.
*/
private String commentId;
/**
* Create a comment add event.
*
* @param commentId Comment ID
*/
public CommentDeleteEvent(String commentId) {
this.commentId = commentId;
}
/**
* Getter of commentId.
*
* @return commentId
*/
public String getCommentId() {
return commentId;
}
}

View File

@@ -0,0 +1,95 @@
package com.sismics.docs.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.SeekBar;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.util.NetworkUtil;
import com.sismics.docs.util.PreferenceUtil;
import java.util.Locale;
/**
* Export PDF dialog fragment.
*
* @author bgamard.
*/
public class DocExportPdfFragment extends DialogFragment {
/**
* Export PDF dialog fragment.
*
* @param id Document ID
* @param title Document title
*/
public static DocExportPdfFragment newInstance(String id, String title) {
DocExportPdfFragment fragment = new DocExportPdfFragment();
Bundle args = new Bundle();
args.putString("id", id);
args.putString("title", title);
fragment.setArguments(args);
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Setup the view
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.document_export_pdf_dialog, null);
final SeekBar marginSeekBar = (SeekBar) view.findViewById(R.id.marginSeekBar);
final CheckBox exportMetadataCheckbox = (CheckBox) view.findViewById(R.id.exportMetadataCheckbox);
final CheckBox exportCommentsCheckbox = (CheckBox) view.findViewById(R.id.exportCommentsCheckbox);
final CheckBox fitToPageCheckbox = (CheckBox) view.findViewById(R.id.fitToPageCheckbox);
final TextView marginValueText = (TextView) view.findViewById(R.id.marginValueText);
// Margin label follow seekbar value
marginSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
marginValueText.setText(String.format(Locale.ENGLISH, "%d", progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// Build the dialog
builder.setView(view)
.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Download the PDF
String pdfUrl = PreferenceUtil.getServerUrl(getActivity()) + "/api/document/" + getArguments().getString("id") + "/pdf?" +
"metadata=" + exportMetadataCheckbox.isChecked() + "&comments=" + exportCommentsCheckbox.isChecked() + "&fitimagetopage=" + fitToPageCheckbox.isChecked() +
"&margin=" + marginSeekBar.getProgress();
String title = getArguments().getString("title");
NetworkUtil.downloadFile(getActivity(), pdfUrl, title + ".pdf", title, getString(R.string.download_pdf_title));
getDialog().cancel();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
getDialog().cancel();
}
});
return builder.create();
}
}

View File

@@ -21,17 +21,17 @@ import com.sismics.docs.event.DocumentAddEvent;
import com.sismics.docs.event.DocumentDeleteEvent;
import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.event.SearchEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.RecyclerItemClickListener;
import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.ui.view.DividerItemDecoration;
import com.sismics.docs.ui.view.EmptyRecyclerView;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONObject;
import de.greenrobot.event.EventBus;
/**
* @author bgamard.
*/
@@ -100,7 +100,7 @@ public class DocListFragment extends Fragment {
}));
// Infinite scrolling
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
@@ -150,6 +150,7 @@ public class DocListFragment extends Fragment {
*
* @param event Search event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(SearchEvent event) {
query = event.getQuery();
loadDocuments(getView(), true);
@@ -160,6 +161,7 @@ public class DocListFragment extends Fragment {
*
* @param event Document edit event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentEditEvent event) {
adapter.updateDocument(event.getDocument());
}
@@ -169,6 +171,7 @@ public class DocListFragment extends Fragment {
*
* @param event Document delete event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentDeleteEvent event) {
adapter.deleteDocument(event.getDocumentId());
}
@@ -178,6 +181,7 @@ public class DocListFragment extends Fragment {
*
* @param event Document add event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(DocumentAddEvent event) {
// Refresh the list, maybe the new document fit in it
loadDocuments(getView(), true);
@@ -218,16 +222,16 @@ public class DocListFragment extends Fragment {
recyclerView.setEmptyView(progressBar);
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new JsonHttpResponseHandler() {
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
adapter.addDocuments(response.optJSONArray("documents"));
documentsEmptyView.setText(R.string.no_documents);
recyclerView.setEmptyView(documentsEmptyView);
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject response, Exception e) {
documentsEmptyView.setText(R.string.error_loading_documents);
recyclerView.setEmptyView(documentsEmptyView);

View File

@@ -21,17 +21,17 @@ import com.sismics.docs.R;
import com.sismics.docs.adapter.ShareListAdapter;
import com.sismics.docs.event.ShareDeleteEvent;
import com.sismics.docs.event.ShareSendEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.resource.ShareResource;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.json.JSONArray;
import org.json.JSONObject;
import de.greenrobot.event.EventBus;
/**
* Document sharing dialog fragment.
*
@@ -44,7 +44,8 @@ public class DocShareFragment extends DialogFragment {
private JSONObject document;
/**
* Document sharing dialog fragment
* Document sharing dialog fragment.
*
* @param id Document ID
*/
public static DocShareFragment newInstance(String id) {
@@ -74,15 +75,15 @@ public class DocShareFragment extends DialogFragment {
shareAddButton.setEnabled(false);
ShareResource.add(getActivity(), getArguments().getString("id"), shareNameEditText.getText().toString(),
new JsonHttpResponseHandler() {
new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
shareNameEditText.setText("");
loadShares(getDialog().getWindow().getDecorView());
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(getActivity(), R.string.error_adding_share, Toast.LENGTH_SHORT).show();
}
@@ -121,9 +122,9 @@ public class DocShareFragment extends DialogFragment {
final ProgressBar shareProgressBar = (ProgressBar) view.findViewById(R.id.shareProgressBar);
shareListView.setEmptyView(shareProgressBar);
DocumentResource.get(getActivity(), getArguments().getString("id"), new JsonHttpResponseHandler() {
DocumentResource.get(getActivity(), getArguments().getString("id"), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
document = response;
JSONArray acls = response.optJSONArray("acls");
shareProgressBar.setVisibility(View.GONE);
@@ -132,27 +133,39 @@ public class DocShareFragment extends DialogFragment {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
getDialog().cancel();
Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show();
}
});
}
/**
* A share delete event has been fired.
*
* @param event Share delete event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(ShareDeleteEvent event) {
ShareResource.delete(getActivity(), event.getId(), new JsonHttpResponseHandler() {
ShareResource.delete(getActivity(), event.getId(), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
loadShares(getDialog().getWindow().getDecorView());
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
Toast.makeText(getActivity(), R.string.error_deleting_share, Toast.LENGTH_SHORT).show();
}
});
}
/**
* A share send event has been fired.
*
* @param event Share send event
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(ShareSendEvent event) {
if (document == null) return;

View File

@@ -22,14 +22,13 @@ import com.sismics.docs.ui.view.TagsCompleteTextView;
import com.sismics.docs.util.PreferenceUtil;
import com.sismics.docs.util.SearchQueryBuilder;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import de.greenrobot.event.EventBus;
/**
* Advanced search fragment.
*
@@ -56,6 +55,7 @@ public class SearchFragment extends DialogFragment {
View view = inflater.inflate(R.layout.search_dialog, null);
final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText);
final EditText fulltextEditText = (EditText) view.findViewById(R.id.fulltextEditText);
final EditText creatorEditText = (EditText) view.findViewById(R.id.creatorEditText);
final CheckBox sharedCheckbox = (CheckBox) view.findViewById(R.id.sharedCheckbox);
final Spinner languageSpinner = (Spinner) view.findViewById(R.id.languageSpinner);
final DatePickerView beforeDatePicker = (DatePickerView) view.findViewById(R.id.beforeDatePicker);
@@ -90,6 +90,7 @@ public class SearchFragment extends DialogFragment {
// Build the simple criterias
SearchQueryBuilder queryBuilder = new SearchQueryBuilder()
.simpleSearch(searchEditText.getText().toString())
.creator(creatorEditText.getText().toString())
.shared(sharedCheckbox.isChecked())
.language(((LanguageAdapter.Language) languageSpinner.getSelectedItem()).getId())
.before(beforeDatePicker.getDate())

View File

@@ -9,10 +9,10 @@ import android.preference.PreferenceManager;
import android.provider.SearchRecentSuggestions;
import android.widget.Toast;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R;
import com.sismics.docs.provider.RecentSuggestionsProvider;
import com.sismics.docs.util.ApplicationUtil;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil;
/**
@@ -52,7 +52,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
clearCachePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AQUtility.cleanCacheAsync(getActivity());
OkHttpUtil.clearCache(getActivity());
Toast.makeText(getActivity(), R.string.pref_clear_cache_success, Toast.LENGTH_LONG).show();
return true;
}

View File

@@ -0,0 +1,78 @@
package com.sismics.docs.listener;
import android.os.Handler;
import android.os.Looper;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
/**
* An HTTP callback.
*
* @author bgamard.
*/
public class HttpCallback {
public void onSuccess(JSONObject json) {
// Implement me
}
public void onFailure(JSONObject json, Exception e) {
// Implement me
}
public void onFinish() {
// Implement me
}
/**
* Build an OkHttp Callback from a HttpCallback.
*
* @param httpCallback HttpCallback
* @return OkHttp Callback
*/
public static Callback buildOkHttpCallback(final HttpCallback httpCallback) {
return new Callback() {
@Override
public void onResponse(final Call call, final Response response) throws IOException {
final String body = response.body().string();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (response.isSuccessful()) {
try {
httpCallback.onSuccess(new JSONObject(body));
} catch (Exception e) {
httpCallback.onFailure(null, e);
}
} else {
try {
httpCallback.onFailure(new JSONObject(body), null);
} catch (Exception e) {
httpCallback.onFailure(null, e);
}
}
httpCallback.onFinish();
}
});
}
@Override
public void onFailure(final Call call, final IOException e) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
httpCallback.onFailure(null, e);
httpCallback.onFinish();
}
});
}
};
}
}

View File

@@ -1,241 +0,0 @@
/*
Android Asynchronous Http Client
Copyright (c) 2011 James Smith <james@loopj.com>
http://loopj.com
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.docs.listener;
import android.util.Log;
import com.loopj.android.http.TextHttpResponseHandler;
import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
* Used to intercept and handle the responses from requests made using {@link com.loopj.android.http.AsyncHttpClient}, with
* automatic parsing into a {@link JSONObject} or {@link JSONArray}. <p>&nbsp;</p> This class is
* designed to be passed to get, post, put and delete requests with the {@link #onSuccess(int,
* org.apache.http.Header[], org.json.JSONArray)} or {@link #onSuccess(int,
* org.apache.http.Header[], org.json.JSONObject)} methods anonymously overridden. <p>&nbsp;</p>
* Additionally, you can override the other event methods from the parent class.
*/
public class JsonHttpResponseHandler extends TextHttpResponseHandler {
private static final String LOG_TAG = "JsonHttpResponseHandler";
/**
* Creates new JsonHttpResponseHandler, with JSON String encoding UTF-8
*/
public JsonHttpResponseHandler() {
super(DEFAULT_CHARSET);
}
/**
* Creates new JsonHttpRespnseHandler with given JSON String encoding
*
* @param encoding String encoding to be used when parsing JSON
*/
public JsonHttpResponseHandler(String encoding) {
super(encoding);
}
/**
* Returns when request succeeds
*
* @param statusCode http response status line
* @param headers response headers if any
* @param response parsed response if any
*/
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONObject) was not overriden, but callback was received");
}
/**
* Returns when request succeeds
*
* @param statusCode http response status line
* @param headers response headers if any
* @param response parsed response if any
*/
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONArray) was not overriden, but callback was received");
}
/**
* Returns when request failed
*
* @param statusCode http response status line
* @param headers response headers if any
* @param throwable throwable describing the way request failed
* @param errorResponse parsed response if any
*/
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONObject) was not overriden, but callback was received", throwable);
}
/**
* Returns when request failed
*
* @param statusCode http response status line
* @param headers response headers if any
* @param throwable throwable describing the way request failed
* @param errorResponse parsed response if any
*/
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) {
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONArray) was not overriden, but callback was received", throwable);
}
@Override
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
Log.w(LOG_TAG, "onFailure(int, Header[], String, Throwable) was not overriden, but callback was received", throwable);
}
@Override
public void onSuccess(int statusCode, Header[] headers, String responseString) {
Log.w(LOG_TAG, "onSuccess(int, Header[], String) was not overriden, but callback was received");
}
@Override
public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBytes) {
if (statusCode != HttpStatus.SC_NO_CONTENT) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final Object jsonResponse = parseResponse(responseBytes);
postRunnable(new Runnable() {
@Override
public void run() {
if (jsonResponse instanceof JSONObject) {
onSuccess(statusCode, headers, (JSONObject) jsonResponse);
} else if (jsonResponse instanceof JSONArray) {
onSuccess(statusCode, headers, (JSONArray) jsonResponse);
} else if (jsonResponse instanceof String) {
onFailure(statusCode, headers, (String) jsonResponse, new JSONException("Response cannot be parsed as JSON data"));
} else {
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
}
}
});
} catch (final JSONException ex) {
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, ex, (JSONObject) null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
onSuccess(statusCode, headers, new JSONObject());
}
}
@Override
public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
if (responseBytes != null) {
Runnable parser = new Runnable() {
@Override
public void run() {
try {
final Object jsonResponse = parseResponse(responseBytes);
postRunnable(new Runnable() {
@Override
public void run() {
if (jsonResponse instanceof JSONObject) {
onFailure(statusCode, headers, throwable, (JSONObject) jsonResponse);
} else if (jsonResponse instanceof JSONArray) {
onFailure(statusCode, headers, throwable, (JSONArray) jsonResponse);
} else if (jsonResponse instanceof String) {
onFailure(statusCode, headers, (String) jsonResponse, throwable);
} else {
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
}
}
});
} catch (final JSONException ex) {
postRunnable(new Runnable() {
@Override
public void run() {
onFailure(statusCode, headers, ex, (JSONObject) null);
}
});
}
}
};
if (!getUseSynchronousMode()) {
new Thread(parser).start();
} else {
// In synchronous mode everything should be run on one thread
parser.run();
}
} else {
Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)");
onFailure(statusCode, headers, throwable, (JSONObject) null);
}
// In all cases, call the default failure listener
onAllFailure(statusCode, headers, responseBytes, throwable);
}
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
// All failures go there
}
/**
* Returns Object of type {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
* Double or {@link JSONObject#NULL}, see {@link org.json.JSONTokener#nextValue()}
*
* @param responseBody response bytes to be assembled in String and parsed as JSON
* @return Object parsedResponse
* @throws org.json.JSONException exception if thrown while parsing JSON
*/
protected Object parseResponse(byte[] responseBody) throws JSONException {
if (null == responseBody)
return null;
Object result = null;
//trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If JSON is not valid this will return null
String jsonString = getResponseString(responseBody, getCharset());
if (jsonString != null) {
jsonString = jsonString.trim();
if (jsonString.startsWith(UTF8_BOM)) {
jsonString = jsonString.substring(1);
}
if (jsonString.startsWith("{") || jsonString.startsWith("[")) {
result = new JSONTokener(jsonString).nextValue();
}
}
if (result == null) {
result = jsonString;
}
return result;
}
}

View File

@@ -11,7 +11,7 @@ public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListen
private OnItemClickListener mListener;
public interface OnItemClickListener {
public void onItemClick(View view, int position);
void onItemClick(View view, int position);
}
GestureDetector mGestureDetector;
@@ -25,13 +25,18 @@ public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListen
});
}
@Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildPosition(childView));
mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
}
return false;
}
@Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
}

View File

@@ -4,11 +4,10 @@ import android.app.Activity;
import android.content.Context;
import com.sismics.docs.listener.CallbackListener;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.resource.UserResource;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject;
/**
@@ -80,9 +79,9 @@ public class ApplicationContext {
* @param callbackListener CallbackListener
*/
public void fetchUserInfo(final Activity activity, final CallbackListener callbackListener) {
UserResource.info(activity.getApplicationContext(), new JsonHttpResponseHandler() {
UserResource.info(activity.getApplicationContext(), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, final JSONObject json) {
public void onSuccess(JSONObject json) {
// Save data in application context
if (!json.optBoolean("anonymous", true)) {
setUserInfo(activity.getApplicationContext(), json);

View File

@@ -0,0 +1,38 @@
package com.sismics.docs.resource;
import android.content.Context;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /auditlog API.
*
* @author bgamard
*/
public class AuditLogResource extends BaseResource {
/**
* GET /auditlog.
*
* @param context Context
* @param documentId Document ID
* @param callback Callback
*/
public static void list(Context context, String documentId, HttpCallback callback) {
HttpUrl.Builder httpUrlBuilder = HttpUrl.parse(getApiUrl(context) + "/auditlog")
.newBuilder();
if (documentId != null) {
httpUrlBuilder.addQueryParameter("document", documentId);
}
Request request = new Request.Builder()
.url(httpUrlBuilder.build())
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -1,125 +1,15 @@
package com.sismics.docs.resource;
import android.content.Context;
import android.os.Build;
import com.androidquery.callback.AbstractAjaxCallback;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.PersistentCookieStore;
import com.sismics.docs.util.ApplicationUtil;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* Base class for API access.
*
* @author bgamard
*/
public class BaseResource {
/**
* User-Agent to use.
*/
protected static String USER_AGENT = null;
/**
* Accept-Language header.
*/
protected static String ACCEPT_LANGUAGE = null;
/**
* HTTP client.
*/
protected static AsyncHttpClient client = new AsyncHttpClient();
static {
// 20sec default timeout
client.setTimeout(60000);
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
client.setSSLSocketFactory(sf);
AbstractAjaxCallback.setSSF(sf);
} catch (Exception e) {
// NOP
}
}
/**
* Resource initialization.
*
* @param context Context
*/
protected static void init(Context context) {
client.setCookieStore(new PersistentCookieStore(context));
if (USER_AGENT == null) {
USER_AGENT = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
client.setUserAgent(USER_AGENT);
}
if (ACCEPT_LANGUAGE == null) {
Locale locale = Locale.getDefault();
ACCEPT_LANGUAGE = locale.getLanguage() + "_" + locale.getCountry();
client.addHeader("Accept-Language", ACCEPT_LANGUAGE);
}
}
/**
* Socket factory to allow self-signed certificates.
*
* @author bgamard
*/
public static class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
/**
* Returns cleaned API URL.
*

View File

@@ -0,0 +1,73 @@
package com.sismics.docs.resource;
import android.content.Context;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /comment API.
*
* @author bgamard
*/
public class CommentResource extends BaseResource {
/**
* GET /comment/id.
*
* @param context Context
* @param documentId Document ID
* @param callback Callback
*/
public static void list(Context context, String documentId, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/comment/" + documentId))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* PUT /comment.
*
* @param context Context
* @param documentId Document ID
* @param content Comment content
* @param callback Callback
*/
public static void add(Context context, String documentId, String content, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/comment"))
.put(new FormBody.Builder()
.add("id", documentId)
.add("content", content)
.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* DELETE /comment/id.
*
* @param context Context
* @param commentId Comment ID
* @param callback Callback
*/
public static void remove(Context context, String commentId, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/comment/" + commentId))
.delete()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -2,11 +2,15 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.loopj.android.http.RequestParams;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import java.util.Set;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /document API.
*
@@ -19,18 +23,23 @@ public class DocumentResource extends BaseResource {
* @param context Context
* @param offset Offset
* @param query Search query
* @param responseHandler Callback
* @param callback Callback
*/
public static void list(Context context, int offset, String query, JsonHttpResponseHandler responseHandler) {
init(context);
RequestParams params = new RequestParams();
params.put("limit", 20);
params.put("offset", offset);
params.put("sort_column", 3);
params.put("asc", false);
params.put("search", query);
client.get(getApiUrl(context) + "/document/list", params, responseHandler);
public static void list(Context context, int offset, String query, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/list")
.newBuilder()
.addQueryParameter("limit", "20")
.addQueryParameter("offset", Integer.toString(offset))
.addQueryParameter("sort_column", "3")
.addQueryParameter("asc", "false")
.addQueryParameter("search", query)
.build())
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -38,12 +47,16 @@ public class DocumentResource extends BaseResource {
*
* @param context Context
* @param id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void get(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.get(getApiUrl(context) + "/document/" + id, responseHandler);
public static void get(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -51,12 +64,16 @@ public class DocumentResource extends BaseResource {
*
* @param context Context
* @param id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.delete(getApiUrl(context) + "/document/" + id, responseHandler);
public static void delete(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.delete()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -68,19 +85,26 @@ public class DocumentResource extends BaseResource {
* @param tagIdList Tags ID list
* @param language Language
* @param createDate Create date
* @param responseHandler Callback
* @param callback Callback
*/
public static void add(Context context, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
init(context);
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
FormBody.Builder formBuilder = new FormBody.Builder()
.add("title", title)
.add("description", description)
.add("language", language)
.add("create_date", Long.toString(createDate));
for( String tagId : tagIdList) {
formBuilder.add("tags", tagId);
}
RequestParams params = new RequestParams();
params.put("title", title);
params.put("description", description);
params.put("tags", tagIdList);
params.put("language", language);
params.put("create_date", createDate);
client.put(getApiUrl(context) + "/document", params, responseHandler);
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document"))
.put(formBuilder.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -93,27 +117,25 @@ public class DocumentResource extends BaseResource {
* @param tagIdList Tags ID list
* @param language Language
* @param createDate Create date
* @param responseHandler Callback
* @param callback Callback
*/
public static void edit(Context context, String id, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
init(context);
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
FormBody.Builder formBuilder = new FormBody.Builder()
.add("title", title)
.add("description", description)
.add("language", language)
.add("create_date", Long.toString(createDate));
for( String tagId : tagIdList) {
formBuilder.add("tags", tagId);
}
RequestParams params = new RequestParams();
params.put("title", title);
params.put("description", description);
params.put("tags", tagIdList);
params.put("language", language);
params.put("create_date", createDate);
client.post(getApiUrl(context) + "/document/" + id, params, responseHandler);
}
/**
* Cancel pending requests.
*
* @param context Context
*/
public static void cancel(Context context) {
client.cancelRequests(context, true);
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.post(formBuilder.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -2,13 +2,24 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.loopj.android.http.PersistentCookieStore;
import com.loopj.android.http.RequestParams;
import com.loopj.android.http.SyncHttpClient;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.Util;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
/**
@@ -22,12 +33,19 @@ public class FileResource extends BaseResource {
*
* @param context Context
* @param documentId Document ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void list(Context context, String documentId, JsonHttpResponseHandler responseHandler) {
init(context);
client.get(getApiUrl(context) + "/file/list?id=" + documentId, responseHandler);
public static void list(Context context, String documentId, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/file/list")
.newBuilder()
.addQueryParameter("id", documentId)
.build())
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -35,12 +53,16 @@ public class FileResource extends BaseResource {
*
* @param context Context
* @param id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.delete(getApiUrl(context) + "/file/" + id, responseHandler);
public static void delete(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/file/" + id))
.delete()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -49,34 +71,53 @@ public class FileResource extends BaseResource {
* @param context Context
* @param documentId Document ID
* @param is Input stream
* @param responseHandler Callback
* @param callback Callback
* @throws Exception
*/
public static void addSync(Context context, String documentId, InputStream is, JsonHttpResponseHandler responseHandler) throws Exception {
init(context);
public static void addSync(Context context, String documentId, final InputStream is, HttpCallback callback) throws Exception {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/file"))
.put(new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("id", documentId)
.addFormDataPart("file", "file", new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse("application/octet-stream");
}
SyncHttpClient client = new SyncHttpClient();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
client.setSSLSocketFactory(sf);
client.setCookieStore(new PersistentCookieStore(context));
client.setUserAgent(USER_AGENT);
client.addHeader("Accept-Language", ACCEPT_LANGUAGE);
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = Okio.source(is);
try {
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
})
.build())
.build();
Response response = OkHttpUtil.buildClient(context)
.newCall(request)
.execute();
RequestParams params = new RequestParams();
params.put("id", documentId);
params.put("file", is, "file", "application/octet-stream", true);
client.put(getApiUrl(context) + "/file", params, responseHandler);
}
// Call the right callback
final String body = response.body().string();
if (response.isSuccessful()) {
try {
callback.onSuccess(new JSONObject(body));
} catch (Exception e) {
callback.onFailure(null, e);
}
} else {
try {
callback.onFailure(new JSONObject(body), null);
} catch (Exception e) {
callback.onFailure(null, e);
}
}
/**
* Cancel pending requests.
*
* @param context Context
*/
public static void cancel(Context context) {
client.cancelRequests(context, true);
callback.onFinish();
}
}

View File

@@ -2,8 +2,12 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.loopj.android.http.RequestParams;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
@@ -18,15 +22,19 @@ public class ShareResource extends BaseResource {
* @param context Context
* @param documentId Document ID
* @param name Name
* @param responseHandler Callback
* @param callback Callback
*/
public static void add(Context context, String documentId, String name, JsonHttpResponseHandler responseHandler) {
init(context);
RequestParams params = new RequestParams();
params.put("id", documentId);
params.put("name", name);
client.put(getApiUrl(context) + "/share", params, responseHandler);
public static void add(Context context, String documentId, String name, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/share"))
.put(new FormBody.Builder()
.add("id", documentId)
.add("name", name)
.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@@ -34,11 +42,15 @@ public class ShareResource extends BaseResource {
*
* @param context Context
* @param id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.delete(getApiUrl(context) + "/share/" + id, responseHandler);
public static void delete(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/share/" + id))
.delete()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -2,7 +2,11 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
@@ -15,11 +19,15 @@ public class TagResource extends BaseResource {
* GET /tag/stats.
*
* @param context Context
* @param responseHandler Callback
* @param callback Callback
*/
public static void stats(Context context, JsonHttpResponseHandler responseHandler) {
init(context);
client.get(getApiUrl(context) + "/tag/stats", responseHandler);
public static void stats(Context context, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/tag/stats"))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -2,8 +2,12 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.loopj.android.http.RequestParams;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /user API.
@@ -18,41 +22,69 @@ public class UserResource extends BaseResource {
* @param context Context
* @param username Username
* @param password Password
* @param responseHandler Callback
* @param callback Callback
*/
public static void login(Context context, String username, String password, JsonHttpResponseHandler responseHandler) {
init(context);
RequestParams params = new RequestParams();
params.put("username", username);
params.put("password", password);
params.put("remember", "true");
client.post(getApiUrl(context) + "/user/login", params, responseHandler);
public static void login(Context context, String username, String password, String code, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/user/login"))
.post(new FormBody.Builder()
.add("username", username)
.add("password", password)
.add("code", code)
.add("remember", "true")
.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* GET /user.
*
* @param context Context
* @param responseHandler Callback
* @param callback Callback
*/
public static void info(Context context, JsonHttpResponseHandler responseHandler) {
init(context);
public static void info(Context context, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/user"))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
RequestParams params = new RequestParams();
client.get(getApiUrl(context) + "/user", params, responseHandler);
/**
* GET /user/username.
*
* @param context Context
* param username Username
* @param callback Callback
*/
public static void get(Context context, String username, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/user/" + username))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* POST /user/logout.
*
* @param context Context
* @param responseHandler Callback
* @param callback Callback
*/
public static void logout(Context context, JsonHttpResponseHandler responseHandler) {
init(context);
RequestParams params = new RequestParams();
client.post(getApiUrl(context) + "/user/logout", params, responseHandler);
public static void logout(Context context, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/user/logout"))
.post(new FormBody.Builder().build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@@ -0,0 +1,229 @@
package com.sismics.docs.resource.cookie;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A persistent cookie store which implements the Apache HttpClient CookieStore interface.
* Cookies are stored and will persist on the user's device between application sessions since they
* are serialized and stored in SharedPreferences.
*/
public class PersistentCookieStore implements CookieStore {
private static final String LOG_TAG = "PersistentCookieStore";
private static final String COOKIE_PREFS = "CookiePrefsFileOkHttp";
private static final String COOKIE_NAME_PREFIX = "cookie_okhttp_";
private final HashMap<String, ConcurrentHashMap<String, HttpCookie>> cookies;
private final SharedPreferences cookiePrefs;
/**
* Construct a persistent cookie store.
*
* @param context Context to attach cookie store to
*/
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new HashMap<>();
// Load any previously stored cookies into the store
Map<String, ?> prefsMap = cookiePrefs.getAll();
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
if (entry.getValue() != null && !((String) entry.getValue()).startsWith(COOKIE_NAME_PREFIX)) {
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
if (encodedCookie != null) {
HttpCookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
if (!cookies.containsKey(entry.getKey()))
cookies.put(entry.getKey(), new ConcurrentHashMap<String, HttpCookie>());
cookies.get(entry.getKey()).put(name, decodedCookie);
}
}
}
}
}
}
@Override
public void add(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
// Save cookie into local store, or remove if expired
if (!cookie.hasExpired()) {
if (!cookies.containsKey(uri.getHost()))
cookies.put(uri.getHost(), new ConcurrentHashMap<String, HttpCookie>());
cookies.get(uri.getHost()).put(name, cookie);
} else {
if (cookies.containsKey(uri.toString()))
cookies.get(uri.getHost()).remove(name);
}
// Save cookie into persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableHttpCookie(cookie)));
prefsWriter.apply();
}
protected String getCookieToken(URI uri, HttpCookie cookie) {
return cookie.getName() + cookie.getDomain();
}
@Override
public List<HttpCookie> get(URI uri) {
ArrayList<HttpCookie> ret = new ArrayList<>();
if (cookies.containsKey(uri.getHost()))
ret.addAll(cookies.get(uri.getHost()).values());
return ret;
}
@Override
public boolean removeAll() {
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.clear();
prefsWriter.apply();
cookies.clear();
return true;
}
@Override
public boolean remove(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
cookies.get(uri.getHost()).remove(name);
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
}
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
prefsWriter.apply();
return true;
} else {
return false;
}
}
@Override
public List<HttpCookie> getCookies() {
ArrayList<HttpCookie> ret = new ArrayList<>();
for (String key : cookies.keySet())
ret.addAll(cookies.get(key).values());
return ret;
}
@Override
public List<URI> getURIs() {
ArrayList<URI> ret = new ArrayList<>();
for (String key : cookies.keySet())
try {
ret.add(new URI(key));
} catch (URISyntaxException e) {
e.printStackTrace();
}
return ret;
}
/**
* Serializes Cookie object into String
*
* @param cookie cookie to be encoded, can be null
* @return cookie encoded as String
*/
protected String encodeCookie(SerializableHttpCookie cookie) {
if (cookie == null)
return null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in encodeCookie", e);
return null;
}
return byteArrayToHexString(os.toByteArray());
}
/**
* Returns cookie decoded from cookie string
*
* @param cookieString string of cookie as returned from http request
* @return decoded cookie or null if exception occured
*/
protected HttpCookie decodeCookie(String cookieString) {
byte[] bytes = hexStringToByteArray(cookieString);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HttpCookie cookie = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
cookie = ((SerializableHttpCookie) objectInputStream.readObject()).getCookie();
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in decodeCookie", e);
} catch (ClassNotFoundException e) {
Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
}
return cookie;
}
/**
* Using some super basic byte array &lt;-&gt; hex conversions so we don't have to rely on any
* large Base64 libraries. Can be overridden if you like!
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
protected String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.US);
}
/**
* Converts hex values from strings to byte arra
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
protected byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
}

View File

@@ -0,0 +1,55 @@
package com.sismics.docs.resource.cookie;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.HttpCookie;
public class SerializableHttpCookie implements Serializable {
private static final long serialVersionUID = 6374381323722046732L;
private transient final HttpCookie cookie;
private transient HttpCookie clientCookie;
public SerializableHttpCookie(HttpCookie cookie) {
this.cookie = cookie;
}
public HttpCookie getCookie() {
HttpCookie bestCookie = cookie;
if (clientCookie != null) {
bestCookie = clientCookie;
}
return bestCookie;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(cookie.getName());
out.writeObject(cookie.getValue());
out.writeObject(cookie.getComment());
out.writeObject(cookie.getCommentURL());
out.writeObject(cookie.getDomain());
out.writeLong(cookie.getMaxAge());
out.writeObject(cookie.getPath());
out.writeObject(cookie.getPortlist());
out.writeInt(cookie.getVersion());
out.writeBoolean(cookie.getSecure());
out.writeBoolean(cookie.getDiscard());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
String name = (String) in.readObject();
String value = (String) in.readObject();
clientCookie = new HttpCookie(name, value);
clientCookie.setComment((String) in.readObject());
clientCookie.setCommentURL((String) in.readObject());
clientCookie.setDomain((String) in.readObject());
clientCookie.setMaxAge(in.readLong());
clientCookie.setPath((String) in.readObject());
clientCookie.setPortlist((String) in.readObject());
clientCookie.setVersion(in.readInt());
clientCookie.setSecure(in.readBoolean());
clientCookie.setDiscard(in.readBoolean());
}
}

View File

@@ -12,16 +12,16 @@ import android.util.Log;
import com.sismics.docs.R;
import com.sismics.docs.event.FileAddEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.resource.FileResource;
import org.apache.http.Header;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import de.greenrobot.event.EventBus;
import okhttp3.internal.Util;
/**
* Service to upload a file to a document in the background.
@@ -81,17 +81,22 @@ public class FileUploadService extends IntentService {
*/
private void handleFileUpload(final String documentId, final Uri uri) throws Exception {
final InputStream is = getContentResolver().openInputStream(uri);
FileResource.addSync(this, documentId, is, new JsonHttpResponseHandler() {
FileResource.addSync(this, documentId, is, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new FileAddEvent(documentId, response.optString("id")));
FileUploadService.this.onComplete();
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
FileUploadService.this.onError();
}
@Override
public void onFinish() {
Util.closeQuietly(is);
}
});
}

View File

@@ -10,7 +10,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
* @author bgamard
*/
public class ApplicationUtil {
/**
* Returns version name.
*

View File

@@ -12,7 +12,6 @@ import com.sismics.docs.R;
* @author bgamard
*/
public class DialogUtil {
/**
* Create a dialog with an OK button.
*

View File

@@ -0,0 +1,33 @@
package com.sismics.docs.util;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
/**
* Utility class for network actions.
*
* @author bgamard.
*/
public class NetworkUtil {
/**
* Download a file using Android download manager.
*
* @param url URL to download
* @param fileName Destination file name
* @param title Notification title
* @param description Notification description
*/
public static void downloadFile(Context context, String url, String fileName, String title, String description) {
String authToken = PreferenceUtil.getAuthToken(context);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
request.addRequestHeader("Cookie", "auth_token=" + authToken);
request.setTitle(title);
request.setDescription(description);
downloadManager.enqueue(request);
}
}

View File

@@ -0,0 +1,187 @@
package com.sismics.docs.util;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.sismics.docs.resource.cookie.PersistentCookieStore;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Utilities for OkHttp.
*
* @author bgamard.
*/
public class OkHttpUtil {
/**
* OkHttp singleton client.
*/
private static OkHttpClient okHttpClient = new OkHttpClient();
/**
* Singleton cache.
*/
private static Cache cache = null;
/**
* User-Agent to use.
*/
protected static String userAgent = null;
/**
* Accept-Language header.
*/
protected static String acceptLanguage = null;
static {
// OkHttp configuration
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// Configure OkHttpClient
okHttpClient = okHttpClient.newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory)
.build();
} catch (Exception e) {
// NOP
}
}
/**
* Build a Picasso object with base config.
*
* @param context Context
* @return Picasso object
*/
public static Picasso picasso(Context context) {
OkHttpClient okHttpClient = buildClient(context)
.newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException { // Override cache configuration
final Request original = chain.request();
return chain.proceed(original.newBuilder()
.header("Cache-Control", "max-age=" + (3600 * 24 * 365))
.method(original.method(), original.body())
.build());
}
})
.cache(getCache(context))
.build();
Picasso picasso = new Picasso.Builder(context)
.downloader(new OkHttp3Downloader(okHttpClient))
.build();
picasso.setIndicatorsEnabled(false); // Debug stuff
return picasso;
}
/**
* Get and eventually build the singleton cache.
*
* @param context Context
* @return Cache
*/
private static Cache getCache(Context context) {
if (cache == null) {
cache = new Cache(context.getCacheDir(),
PreferenceUtil.getIntegerPreference(context, PreferenceUtil.PREF_CACHE_SIZE, 0) * 1000000);
}
return cache;
}
/**
* Clear the HTTP cache.
*
* @param context Context
*/
public static void clearCache(Context context) {
Cache cache = getCache(context);
try {
cache.evictAll();
} catch (IOException e) {
Log.e("OKHttpUtil", "Error clearing cache", e);
}
}
/**
* Build an OkHttpClient.
*
* @param context Context
* @return OkHttpClient
*/
public static OkHttpClient buildClient(final Context context) {
// One-time header computation
if (userAgent == null) {
userAgent = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
}
if (acceptLanguage == null) {
Locale locale = Locale.getDefault();
acceptLanguage = locale.getLanguage() + "_" + locale.getCountry();
}
// Cookie handling
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
CookieManager cookieManager = new CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL);
// Runtime configuration
return okHttpClient.newBuilder()
.cookieJar(new JavaNetCookieJar(cookieManager))
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
return chain.proceed(original.newBuilder()
.header("User-Agent", userAgent)
.header("Accept-Language", acceptLanguage)
.method(original.method(), original.body())
.build());
}
})
.build();
}
}

View File

@@ -5,11 +5,11 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import com.loopj.android.http.PersistentCookieStore;
import com.sismics.docs.resource.cookie.PersistentCookieStore;
import org.apache.http.cookie.Cookie;
import org.json.JSONObject;
import java.net.HttpCookie;
import java.util.List;
/**
@@ -26,6 +26,7 @@ public class PreferenceUtil {
/**
* Returns a preference of boolean type.
*
* @param context Context
* @param key Shared preference key
* @return Shared preference value
@@ -37,6 +38,7 @@ public class PreferenceUtil {
/**
* Returns a preference of string type.
*
* @param context Context
* @param key Shared preference key
* @return Shared preference value
@@ -48,6 +50,7 @@ public class PreferenceUtil {
/**
* Returns a preference of integer type.
*
* @param context Context
* @param key Shared preference key
* @return Shared preference value
@@ -69,6 +72,7 @@ public class PreferenceUtil {
/**
* Update JSON cache.
*
* @param context Context
* @param key Shared preference key
* @param json JSON data
@@ -80,6 +84,7 @@ public class PreferenceUtil {
/**
* Returns a JSON cache.
*
* @param context Context
* @param key Shared preference key
* @return JSON data
@@ -96,6 +101,7 @@ public class PreferenceUtil {
/**
* Update server URL.
*
* @param context Context
*/
public static void setServerUrl(Context context, String serverUrl) {
@@ -105,6 +111,7 @@ public class PreferenceUtil {
/**
* Empty user caches.
*
* @param context Context
*/
public static void resetUserCache(Context context) {
@@ -118,12 +125,13 @@ public class PreferenceUtil {
/**
* Returns auth token cookie from shared preferences.
*
* @return Auth token
*/
public static String getAuthToken(Context context) {
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
List<Cookie> cookieList = cookieStore.getCookies();
for (Cookie cookie : cookieList) {
List<HttpCookie> cookieList = cookieStore.getCookies();
for (HttpCookie cookie : cookieList) {
if (cookie.getName().equals("auth_token")) {
return cookie.getValue();
}
@@ -134,6 +142,7 @@ public class PreferenceUtil {
/**
* Returns cleaned server URL.
*
* @param context Context
* @return Server URL
*/

View File

@@ -59,6 +59,21 @@ public class SearchQueryBuilder {
return this;
}
/**
* Add a creator criteria.
*
* @param creator Creator criteria
* @return The builder
*/
public SearchQueryBuilder creator(String creator) {
if (isValid(creator)) {
query.append(SEARCH_SEPARATOR)
.append("by:")
.append(creator);
}
return this;
}
/**
* Add a language criteria.
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/auditLogListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:dividerHeight="0dp"
android:visibility="gone">
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/assignImageView"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_assignment_grey600_48dp"/>
<TextView
android:id="@+id/usernameTextView"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/assignImageView"
android:layout_toEndOf="@+id/assignImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#212121"
android:text="admin"
android:textSize="16sp"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/messageTextView"
android:layout_below="@+id/usernameTextView"
android:layout_toRightOf="@+id/assignImageView"
android:layout_toEndOf="@+id/assignImageView"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#777777"
android:text="Document created : test doc 1"
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end"/>
<TextView
android:id="@+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2014-12-02"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:textColor="#777777"
android:fontFamily="sans-serif-light"/>
</RelativeLayout>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/gravatarImageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginRight="12dp"/>
<TextView
android:id="@+id/creatorTextView"
android:layout_toRightOf="@id/gravatarImageView"
android:layout_toEndOf="@id/gravatarImageView"
android:layout_alignParentTop="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textStyle="bold"
android:textColor="#212121"
android:text="Creator"
android:textSize="14sp"/>
<TextView
android:id="@+id/contentTextView"
android:layout_toRightOf="@id/gravatarImageView"
android:layout_toEndOf="@id/gravatarImageView"
android:layout_below="@id/creatorTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:fontFamily="sans-serif"
android:textColor="#212121"
android:text="Comment content"
android:textSize="14sp"/>
<TextView
android:id="@+id/dateTextView"
android:layout_toRightOf="@id/gravatarImageView"
android:layout_toEndOf="@id/gravatarImageView"
android:layout_below="@id/contentTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:fontFamily="sans-serif"
android:textColor="#888"
android:text="2015-11-10"
android:textSize="14sp"/>
</RelativeLayout>

View File

@@ -37,7 +37,7 @@
android:textSize="16sp"
android:layout_centerInParent="true"/>
<com.shamanland.fab.FloatingActionButton
<android.support.design.widget.FloatingActionButton
android:id="@+id/addDocumentButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -48,6 +48,6 @@
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:src="@drawable/ic_add_white_24dp"
app:floatingActionButtonColor="#263238"/>
app:fabSize="normal"/>
</RelativeLayout>

View File

@@ -13,6 +13,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:id="@+id/folderImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -22,7 +23,9 @@
android:id="@+id/titleTextView"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/folderImageView"
android:layout_toEndOf="@+id/folderImageView"
android:layout_toLeftOf="@+id/dateTextView"
android:layout_toStartOf="@+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:text="@string/export_metadata"
android:id="@+id/exportMetadataCheckbox" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:text="@string/export_comments"
android:id="@+id/exportCommentsCheckbox" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:checked="true"
android:text="@string/fit_image_to_page"
android:id="@+id/fitToPageCheckbox" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/margin"
android:layout_weight="0"/>
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/marginSeekBar"
android:progress="10"
android:max="50"/>
<TextView
android:id="@+id/marginValueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="10"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_weight="0"
android:text="@string/mm"/>
</LinearLayout>
</LinearLayout>

View File

@@ -37,6 +37,109 @@
</RelativeLayout>
<!-- Left drawer -->
<LinearLayout
android:id="@+id/left_drawer"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
android:clickable="true"
android:background="#fff"
android:elevation="5dp">
<!-- Comments -->
<TextView
android:drawableStart="@drawable/ic_comment_grey600_24dp"
android:drawableLeft="@drawable/ic_comment_grey600_24dp"
android:drawablePadding="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:gravity="center"
android:textColor="#de000000"
android:text="@string/comments"
android:layout_margin="12dp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee"/>
<ListView
android:layout_weight="1"
android:id="@+id/commentListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:transcriptMode="normal"
android:dividerHeight="0dp"/>
<RelativeLayout
android:layout_weight="1"
android:id="@+id/commentProgressView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone">
<ProgressBar
style="?android:progressBarStyle"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</RelativeLayout>
<TextView
android:id="@+id/commentEmptyView"
android:visibility="gone"
android:padding="12dp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fontFamily="sans-serif-light"
android:text="@string/no_comments"
android:textSize="14sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="6dp"
android:gravity="center">
<EditText
android:id="@+id/commentEditText"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:lines="1"
android:inputType="text"
android:hint="@string/add_comment"
android:maxLength="4000"/>
<ImageButton
android:id="@+id/addCommentBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_send_grey600_24dp"
android:contentDescription="@string/send"
android:background="?android:selectableItemBackground"/>
</LinearLayout>
</LinearLayout>
<!-- Right drawer -->
<LinearLayout
@@ -70,7 +173,7 @@
android:drawableTop="@drawable/ic_create_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/edit_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@@ -81,7 +184,7 @@
android:drawableTop="@drawable/ic_file_upload_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/upload_file"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@@ -92,7 +195,7 @@
android:drawableTop="@drawable/ic_file_download_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/download_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@@ -104,6 +207,17 @@
android:orientation="horizontal"
style="?android:buttonBarStyle">
<Button
android:id="@+id/actionExportPdf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_description_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/export_pdf"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionSharing"
android:layout_width="wrap_content"
@@ -111,9 +225,20 @@
android:drawableTop="@drawable/ic_share_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/share"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionAuditLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_assignment_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/activity"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionDelete"
@@ -122,9 +247,9 @@
android:drawableTop="@drawable/ic_delete_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/delete_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
android:layout_margin="0dp"/>
</LinearLayout>
@@ -166,12 +291,33 @@
android:layout_alignParentTop="true"
android:fontFamily="sans-serif-light"/>
<TextView
android:id="@+id/creatorLabel"
android:layout_width="100dp"
android:layout_height="24dp"
android:gravity="center_vertical"
android:layout_below="@+id/createdDateLabel"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:fontFamily="sans-serif"
android:text="@string/creator"/>
<TextView
android:id="@+id/creatorTextView"
android:layout_toRightOf="@id/creatorLabel"
android:layout_toEndOf="@id/creatorLabel"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:gravity="center_vertical"
android:layout_below="@+id/createdDateTextView"
android:fontFamily="sans-serif-light"/>
<TextView
android:id="@+id/tagTextView"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/createdDateLabel"
android:layout_below="@id/creatorLabel"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:maxLines="1"
@@ -219,7 +365,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/primary_text_default_material_light"
android:textColor="#de000000"
android:text="@string/who_can_access"
android:layout_margin="12dp"/>

View File

@@ -9,7 +9,7 @@
android:layout_width="200dp"
android:layout_height="15dip"
android:id="@+id/fileProgressBar"
android:indeterminate="false"
android:indeterminate="true"
android:layout_centerInParent="true"/>
<it.sephiroth.android.library.imagezoom.ImageViewTouch

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/membersTextView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@@ -61,6 +61,17 @@
android:inputType="textPassword">
</EditText>
<EditText
android:visibility="gone"
android:id="@+id/txtValidationCode"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:ems="10"
android:hint="@string/validation_code"
android:inputType="number">
</EditText>
<Button
android:id="@+id/btnConnect"
android:layout_width="fill_parent"

View File

@@ -117,6 +117,40 @@
</RelativeLayout>
<!-- Audit log -->
<RelativeLayout
android:id="@+id/auditLogLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/auditLogImageView"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_assignment_grey600_24dp"/>
<TextView
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/auditLogImageView"
android:layout_toEndOf="@+id/auditLogImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textColor="#212121"
android:text="@string/latest_activity"
android:textSize="14sp"/>
</RelativeLayout>
<!-- Separator -->
<View

View File

@@ -27,6 +27,15 @@
android:textSize="18sp"
android:hint="@string/fulltext_search"/>
<!-- Creator -->
<EditText
android:id="@+id/creatorEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textSize="18sp"
android:hint="@string/creator"/>
<!-- Language -->
<Spinner
android:id="@+id/languageSpinner"

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:gravity="end"
android:textSize="16sp"
android:text="@string/email"/>
<TextView
android:id="@+id/emailTextView"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:text="user1@sismicsdocs.com"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:gravity="end"
android:textSize="16sp"
android:text="@string/storage_quota"/>
<TextView
android:id="@+id/quotaTextView"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:text="35/500 MB"/>
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@@ -9,6 +9,12 @@
android:title="@string/toggle_informations">
</item>
<item
android:id="@+id/comments"
app:showAsAction="collapseActionView"
android:title="@string/comments">
</item>
<item
android:id="@+id/download_file"
app:showAsAction="collapseActionView"

View File

@@ -28,9 +28,7 @@
<string name="crash_toast_text">A crash occurred, a report has been sent to resolve this problem</string>
<string name="created_date">Created date</string>
<string name="download_file">Download current file</string>
<string name="downloading_file">Downloading file number %1s</string>
<string name="download_document">Download</string>
<string name="downloading_document">Downloading document</string>
<string name="action_search">Search documents</string>
<string name="all_documents">All documents</string>
<string name="shared_documents">Shared documents</string>
@@ -101,12 +99,38 @@
<string name="title">Title</string>
<string name="simple_search">Simple search</string>
<string name="fulltext_search">Fulltext search</string>
<string name="creator">Creator</string>
<string name="after_date">After date</string>
<string name="before_date">Before date</string>
<string name="search_tags">Search tags</string>
<string name="all_languages">All languages</string>
<string name="toggle_informations">Toggle informations</string>
<string name="who_can_access">Who can access</string>
<string name="comments">Comments</string>
<string name="no_comments">No comments</string>
<string name="error_loading_comments">Error loading comments</string>
<string name="send">Send</string>
<string name="add_comment">Add a comment</string>
<string name="comment_add_failure">Error adding a comment</string>
<string name="adding_comment">Adding a comment</string>
<string name="comment_delete">Delete comment</string>
<string name="deleting_comment">Deleting comment</string>
<string name="error_deleting_comment">Error deleting comment</string>
<string name="export_pdf">PDF</string>
<string name="download">Download</string>
<string name="margin">Margin</string>
<string name="fit_image_to_page">Fit image to page</string>
<string name="export_comments">Export comments</string>
<string name="export_metadata">Export metadata</string>
<string name="mm">mm</string>
<string name="download_file_title">Sismics Docs file export</string>
<string name="download_document_title">Sismics Docs document export</string>
<string name="download_pdf_title">Sismics Docs PDF export</string>
<string name="latest_activity">Latest activity</string>
<string name="activity">Activity</string>
<string name="email">E-mail</string>
<string name="storage_quota">Storage quota</string>
<string name="storage_display">%1$d/%2$d MB</string>
<string name="validation_code">Validation code</string>
</resources>

View File

@@ -1,6 +1,6 @@
#Wed Nov 26 21:58:48 CET 2014
#Sat Jan 16 19:15:13 CET 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

View File

@@ -5,8 +5,8 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
<version>1.4</version>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -26,6 +26,11 @@
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
</dependency>
<!-- Other external dependencies -->
<dependency>
<groupId>joda-time</groupId>
@@ -72,26 +77,6 @@
<artifactId>jbcrypt</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>org.ccil.cowan.tagsoup</groupId>
<artifactId>tagsoup</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
@@ -107,9 +92,10 @@
<artifactId>lucene-queryparser</artifactId>
</dependency>
<!-- Only there to read old index and rebuild them -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<artifactId>lucene-backward-codecs</artifactId>
</dependency>
<dependency>
@@ -127,15 +113,35 @@
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<!-- OCR dependencies -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.odftoolkit.odfdom.converter.pdf</artifactId>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
</dependency>
<dependency>
<groupId>jna</groupId>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<!-- ImageIO plugins -->
<dependency>
<groupId>jai</groupId>
<artifactId>imageio</artifactId>
<groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
</dependency>
<!-- Test dependencies -->
@@ -146,8 +152,8 @@
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1 @@
update T_CONFIG set CFG_VALUE_C = 'RAM' where CFG_ID_C = 'LUCENE_DIRECTORY_STORAGE';

View File

@@ -10,4 +10,8 @@ public enum ConfigType {
* Lucene directory storage type.
*/
LUCENE_DIRECTORY_STORAGE,
/**
* Theme configuration.
*/
THEME
}

View File

@@ -10,21 +10,11 @@ import com.google.common.collect.Lists;
* @author jtremeaux
*/
public class Constants {
/**
* Default locale.
*/
public static final String DEFAULT_LOCALE_ID = "en";
/**
* Default timezone ID.
*/
public static final String DEFAULT_TIMEZONE_ID = "Europe/London";
/**
* Default theme ID.
*/
public static final String DEFAULT_THEME_ID = "default.less";
/**
* Administrator's default password ("admin").
*/

View File

@@ -1,38 +0,0 @@
package com.sismics.docs.core.dao.file.theme;
import com.google.common.collect.Lists;
import com.sismics.docs.core.util.DirectoryUtil;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
/**
* Theme DAO.
*
* @author jtremeaux
*/
public class ThemeDao {
private final static FilenameFilter CSS_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".css") || name.endsWith(".less");
}
};
/**
* Return the list of all themes.
*
* @return List of themes
*/
public List<String> findAll() {
final File themeDirectory = DirectoryUtil.getThemeDirectory();
if (themeDirectory != null) {
return Lists.newArrayList(themeDirectory.list(CSS_FILTER));
} else {
return new ArrayList<String>();
}
}
}

View File

@@ -26,10 +26,10 @@ public class AclDao {
* Creates a new ACL.
*
* @param acl ACL
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Acl acl) {
public String create(Acl acl, String userId) {
// Create the UUID
acl.setId(UUID.randomUUID().toString());
@@ -38,7 +38,7 @@ public class AclDao {
em.persist(acl);
// Create audit log
AuditLogUtil.create(acl, AuditLogType.CREATE);
AuditLogUtil.create(acl, AuditLogType.CREATE, userId);
return acl.getId();
}
@@ -67,10 +67,12 @@ public class AclDao {
@SuppressWarnings("unchecked")
public List<AclDto> getBySourceId(String sourceId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, u.USE_USERNAME_C, s.SHA_NAME_C");
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, ");
sb.append(" u.USE_USERNAME_C, s.SHA_ID_C, s.SHA_NAME_C, g.GRP_NAME_C ");
sb.append(" from T_ACL a ");
sb.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_GROUP g on g.GRP_ID_C = a.ACL_TARGETID_C ");
sb.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId ");
// Perform the query
@@ -79,7 +81,7 @@ public class AclDao {
List<Object[]> l = q.getResultList();
// Assemble results
List<AclDto> aclDtoList = new ArrayList<AclDto>();
List<AclDto> aclDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
AclDto aclDto = new AclDto();
@@ -87,10 +89,21 @@ public class AclDao {
aclDto.setPerm(PermType.valueOf((String) o[i++]));
aclDto.setTargetId((String) o[i++]);
String userName = (String) o[i++];
String shareId = (String) o[i++];
String shareName = (String) o[i++];
aclDto.setTargetName(userName == null ? shareName : userName);
aclDto.setTargetType(userName == null ?
AclTargetType.SHARE.name() : AclTargetType.USER.name());
String groupName = (String) o[i];
if (userName != null) {
aclDto.setTargetName(userName);
aclDto.setTargetType(AclTargetType.USER.name());
}
if (shareId != null) { // Use ID because share name is nullable
aclDto.setTargetName(shareName);
aclDto.setTargetType(AclTargetType.SHARE.name());
}
if (groupName != null) {
aclDto.setTargetName(groupName);
aclDto.setTargetType(AclTargetType.GROUP.name());
}
aclDtoList.add(aclDto);
}
return aclDtoList;
@@ -100,23 +113,25 @@ public class AclDao {
* Check if a source is accessible to a target.
*
* @param sourceId ACL source entity ID
* @parm perm Necessary permission
* @param targetId ACL target entity ID
* @param perm Necessary permission
* @param targetIdList List of targets
* @return True if the document is accessible
*/
public boolean checkPermission(String sourceId, PermType perm, String targetId) {
public boolean checkPermission(String sourceId, PermType perm, List<String> targetIdList) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.deleteDate is null");
StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a ");
sb.append(" where a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = :sourceId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" union all ");
sb.append(" select a.ACL_ID_C from T_ACL a, T_DOCUMENT_TAG dt ");
sb.append(" where a.ACL_SOURCEID_C = dt.DOT_IDTAG_C and dt.DOT_IDDOCUMENT_C = :sourceId and dt.DOT_DELETEDATE_D is null ");
sb.append(" and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("sourceId", sourceId);
q.setParameter("perm", perm);
q.setParameter("targetId", targetId);
q.setParameter("perm", perm.name());
q.setParameter("targetIdList", targetIdList);
// We have a matching permission
if (q.getResultList().size() > 0) {
return true;
}
return false;
return q.getResultList().size() > 0;
}
/**
@@ -125,9 +140,10 @@ public class AclDao {
* @param sourceId Source ID
* @param perm Permission
* @param targetId Target ID
* @param userId User ID
*/
@SuppressWarnings("unchecked")
public void delete(String sourceId, PermType perm, String targetId) {
public void delete(String sourceId, PermType perm, String targetId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Create audit log
@@ -137,7 +153,7 @@ public class AclDao {
q.setParameter("targetId", targetId);
List<Acl> aclList = q.getResultList();
for (Acl acl : aclList) {
AuditLogUtil.create(acl, AuditLogType.DELETE);
AuditLogUtil.create(acl, AuditLogType.DELETE, userId);
}
// Soft delete the ACLs

View File

@@ -11,6 +11,7 @@ import java.util.UUID;
import javax.persistence.EntityManager;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.AuditLogCriteria;
import com.sismics.docs.core.dao.jpa.dto.AuditLogDto;
@@ -53,41 +54,33 @@ public class AuditLogDao {
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of audit logs
* @throws Exception
*/
public void findByCriteria(PaginatedList<AuditLogDto> paginatedList, AuditLogCriteria criteria, SortCriteria sortCriteria) throws Exception {
public void findByCriteria(PaginatedList<AuditLogDto> paginatedList, AuditLogCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
StringBuilder sb = new StringBuilder("select l.LOG_ID_C c0, l.LOG_CREATEDATE_D c1, l.LOG_IDENTITY_C c2, l.LOG_CLASSENTITY_C c3, l.LOG_TYPE_C c4, l.LOG_MESSAGE_C c5 ");
sb.append(" from T_AUDIT_LOG l ");
StringBuilder baseQuery = new StringBuilder("select l.LOG_ID_C c0, l.LOG_CREATEDATE_D c1, u.USE_USERNAME_C c2, l.LOG_IDENTITY_C c3, l.LOG_CLASSENTITY_C c4, l.LOG_TYPE_C c5, l.LOG_MESSAGE_C c6 from T_AUDIT_LOG l ");
baseQuery.append(" join T_USER u on l.LOG_IDUSER_C = u.USE_ID_C ");
List<String> queries = Lists.newArrayList();
// Adds search criteria
if (criteria.getDocumentId() != null) {
// ACL on document is not checked here, it's assumed
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :documentId and l.LOG_CLASSENTITY_C = 'Document' ");
sb0.append(" or l.LOG_IDENTITY_C in (select f.FIL_ID_C from T_FILE f where f.FIL_IDDOC_C = :documentId) and l.LOG_CLASSENTITY_C = 'File' ");
sb0.append(" or l.LOG_IDENTITY_C in (select a.ACL_ID_C from T_ACL a where a.ACL_SOURCEID_C = :documentId) and l.LOG_CLASSENTITY_C = 'Acl') ");
criteriaList.add(sb0.toString());
// ACL on document is not checked here, rights have been checked before
queries.add(baseQuery + " where l.LOG_IDENTITY_C = :documentId ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select f.FIL_ID_C from T_FILE f where f.FIL_IDDOC_C = :documentId) ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select c.COM_ID_C from T_COMMENT c where c.COM_IDDOC_C = :documentId) ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select a.ACL_ID_C from T_ACL a where a.ACL_SOURCEID_C = :documentId) ");
parameterMap.put("documentId", criteria.getDocumentId());
}
if (criteria.getUserId() != null) {
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :userId and l.LOG_CLASSENTITY_C = 'User' ");
sb0.append(" or l.LOG_IDENTITY_C in (select t.TAG_ID_C from T_TAG t where t.TAG_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Tag' ");
// Show only logs from owned documents, ACL are lost on delete
sb0.append(" or l.LOG_IDENTITY_C in (select d.DOC_ID_C from T_DOCUMENT d where d.DOC_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Document') ");
criteriaList.add(sb0.toString());
// Get all logs originating from the user, not necessarly on owned items
// Filter out ACL logs
queries.add(baseQuery + " where l.LOG_IDUSER_C = :userId and l.LOG_CLASSENTITY_C != 'Acl' ");
parameterMap.put("userId", criteria.getUserId());
}
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = new QueryParam(sb.toString(), parameterMap);
QueryParam queryParam = new QueryParam(Joiner.on(" union ").join(queries), parameterMap);
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
// Assemble results
@@ -97,6 +90,7 @@ public class AuditLogDao {
AuditLogDto auditLogDto = new AuditLogDto();
auditLogDto.setId((String) o[i++]);
auditLogDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
auditLogDto.setUsername((String) o[i++]);
auditLogDto.setEntityId((String) o[i++]);
auditLogDto.setEntityClass((String) o[i++]);
auditLogDto.setType(AuditLogType.valueOf((String) o[i++]));

View File

@@ -0,0 +1,116 @@
package com.sismics.docs.core.dao.jpa;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.dto.CommentDto;
import com.sismics.docs.core.model.jpa.Comment;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
/**
* Comment DAO.
*
* @author bgamard
*/
public class CommentDao {
/**
* Creates a new comment.
*
* @param comment Comment
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Comment comment, String userId) {
// Create the UUID
comment.setId(UUID.randomUUID().toString());
// Create the comment
EntityManager em = ThreadLocalContext.get().getEntityManager();
comment.setCreateDate(new Date());
em.persist(comment);
// Create audit log
AuditLogUtil.create(comment, AuditLogType.CREATE, userId);
return comment.getId();
}
/**
* Deletes a comment.
*
* @param id Comment ID
* @param userId User ID
*/
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the comment
Query q = em.createQuery("select c from Comment c where c.id = :id and c.deleteDate is null");
q.setParameter("id", id);
Comment commentDb = (Comment) q.getSingleResult();
// Delete the comment
Date dateNow = new Date();
commentDb.setDeleteDate(dateNow);
// Create audit log
AuditLogUtil.create(commentDb, AuditLogType.DELETE, userId);
}
/**
* Gets an active comment by its ID.
*
* @param id Comment ID
* @return Comment
*/
public Comment getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select c from Comment c where c.id = :id and c.deleteDate is null");
q.setParameter("id", id);
return (Comment) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Get all comments on a document.
*
* @param documentId Document ID
* @return List of comments
*/
public List<CommentDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select c.COM_ID_C, c.COM_CONTENT_C, c.COM_CREATEDATE_D, u.USE_USERNAME_C, u.USE_EMAIL_C from T_COMMENT c, T_USER u");
sb.append(" where c.COM_IDDOC_C = :documentId and c.COM_IDUSER_C = u.USE_ID_C and c.COM_DELETEDATE_D is null ");
sb.append(" order by c.COM_CREATEDATE_D asc ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
@SuppressWarnings("unchecked")
List<Object[]> l = q.getResultList();
List<CommentDto> commentDtoList = new ArrayList<CommentDto>();
for (Object[] o : l) {
int i = 0;
CommentDto commentDto = new CommentDto();
commentDto.setId((String) o[i++]);
commentDto.setContent((String) o[i++]);
commentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
commentDto.setCreatorName((String) o[i++]);
commentDto.setCreatorEmail((String) o[i++]);
commentDtoList.add(commentDto);
}
return commentDtoList;
}
}

View File

@@ -33,4 +33,23 @@ public class ConfigDao {
return null;
}
}
/**
* Updates a configuration parameter.
*
* @param id Configuration parameter ID
* @param value Configuration parameter value
*/
public void update(ConfigType id, String value) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Config config = getById(id);
if (config == null) {
config = new Config();
config.setId(id);
config.setValue(value);
em.persist(config);
} else {
config.setValue(value);
}
}
}

View File

@@ -0,0 +1,78 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.dao.jpa.dto.ContributorDto;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.util.context.ThreadLocalContext;
/**
* Contributor DAO.
*
* @author bgamard
*/
public class ContributorDao {
/**
* Creates a new contributor.
*
* @param contributor Contributor
* @return New ID
*/
public String create(Contributor contributor) {
// Create the UUID
contributor.setId(UUID.randomUUID().toString());
// Create the contributor
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(contributor);
return contributor.getId();
}
/**
* Returns the list of all contributors by document.
*
* @param documentId Document ID
* @return List of contributors
*/
@SuppressWarnings("unchecked")
public List<Contributor> findByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select c from Contributor c where c.documentId = :documentId");
q.setParameter("documentId", documentId);
return q.getResultList();
}
/**
* Returns the list of all contributors by document.
*
* @param documentId Document ID
* @return List of contributors
*/
@SuppressWarnings("unchecked")
public List<ContributorDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
sb.append(" where c.CTR_IDDOC_C = :documentId ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<ContributorDto> contributorDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
ContributorDto contributorDto = new ContributorDto();
contributorDto.setUsername((String) o[i++]);
contributorDto.setEmail((String) o[i]);
contributorDtoList.add(contributorDto);
}
return contributorDtoList;
}
}

View File

@@ -38,10 +38,10 @@ public class DocumentDao {
* Creates a new document.
*
* @param document Document
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Document document) {
public String create(Document document, String userId) {
// Create the UUID
document.setId(UUID.randomUUID().toString());
@@ -50,13 +50,13 @@ public class DocumentDao {
em.persist(document);
// Create audit log
AuditLogUtil.create(document, AuditLogType.CREATE);
AuditLogUtil.create(document, AuditLogType.CREATE, userId);
return document.getId();
}
/**
* Returns the list of all documents.
* Returns the list of all active documents.
*
* @return List of documents
*/
@@ -68,61 +68,80 @@ public class DocumentDao {
}
/**
* Returns an active document.
* Returns the list of all active documents from a user.
*
* @param userId User ID
* @return List of documents
*/
@SuppressWarnings("unchecked")
public List<Document> findByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.userId = :userId and d.deleteDate is null");
q.setParameter("userId", userId);
return q.getResultList();
}
/**
* Returns an active document with permission checking.
*
* @param id Document ID
* @param perm Permission needed
* @param targetIdList List of targets
* @return Document
*/
public DocumentDto getDocument(String id) {
public DocumentDto getDocument(String id, PermType perm, List<String> targetIdList) {
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, perm, targetIdList)) {
return null;
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, ");
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), ");
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), ");
sb.append(" u.USE_USERNAME_C ");
sb.append(" from T_DOCUMENT d, T_USER u ");
sb.append(" where d.DOC_IDUSER_C = u.USE_ID_C and d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null ");
sb.append(" from T_DOCUMENT d ");
sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C ");
sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("id", id);
Object[] o = (Object[]) q.getSingleResult();
Object[] o;
try {
o = (Object[]) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
DocumentDto documentDto = new DocumentDto();
int i = 0;
documentDto.setId((String) o[i++]);
documentDto.setTitle((String) o[i++]);
documentDto.setDescription((String) o[i++]);
documentDto.setSubject((String) o[i++]);
documentDto.setIdentifier((String) o[i++]);
documentDto.setPublisher((String) o[i++]);
documentDto.setFormat((String) o[i++]);
documentDto.setSource((String) o[i++]);
documentDto.setType((String) o[i++]);
documentDto.setCoverage((String) o[i++]);
documentDto.setRights((String) o[i++]);
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
documentDto.setCreator((String) o[i++]);
documentDto.setCreator((String) o[i]);
return documentDto;
}
/**
* Returns an active document.
*
* @param id Document ID
* @param perm Permission needed
* @param userId User ID
* @return Document
*/
public Document getDocument(String id, PermType perm, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createNativeQuery("select d.* from T_DOCUMENT d "
+ " join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "
+ " where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null", Document.class);
q.setParameter("id", id);
q.setParameter("perm", perm.name());
q.setParameter("userId", userId);
return (Document) q.getSingleResult();
}
/**
* Deletes a document.
*
* @param id Document ID
* @param userId User ID
*/
public void delete(String id) {
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
@@ -140,31 +159,37 @@ public class DocumentDao {
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// TODO Delete share from deleted ACLs
// q = em.createQuery("update Share s set s.deleteDate = :dateNow where s.documentId = :documentId and s.deleteDate is null");
// q.setParameter("documentId", id);
// q.setParameter("dateNow", dateNow);
// q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :documentId and a.deleteDate is null");
q.setParameter("documentId", id);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :documentId");
q = em.createQuery("update DocumentTag dt set dt.deleteDate = :dateNow where dt.documentId = :documentId and dt.deleteDate is not null");
q.setParameter("documentId", id);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Relation r set r.deleteDate = :dateNow where (r.fromDocumentId = :documentId or r.toDocumentId = :documentId) and r.deleteDate is not null");
q.setParameter("documentId", id);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.DELETE);
AuditLogUtil.create(documentDb, AuditLogType.DELETE, userId);
}
/**
* Gets a document by its ID.
* Gets an active document by its ID.
*
* @param id Document ID
* @return Document
*/
public Document getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
q.setParameter("id", id);
try {
return em.find(Document.class, id);
return (Document) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
@@ -176,23 +201,25 @@ public class DocumentDao {
* @param paginatedList List of documents (updated by side effects)
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @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>();
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
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, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is 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 ");
// Adds search criteria
if (criteria.getUserId() != null) {
// Add search criterias
if (criteria.getTargetIdList() != null) {
// Read permission is enough for searching
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
parameterMap.put("userId", criteria.getUserId());
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null ");
sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null ");
criteriaList.add("(a.ACL_ID_C is not null or a2.ACL_ID_C is not null)");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
LuceneDao luceneDao = new LuceneDao();
@@ -215,8 +242,7 @@ public class DocumentDao {
if (criteria.getTagIdList() != null && !criteria.getTagIdList().isEmpty()) {
int index = 0;
for (String tagId : criteria.getTagIdList()) {
sb.append(" left join T_DOCUMENT_TAG dt" + index + " on dt" + index + ".DOT_IDDOCUMENT_C = d.DOC_ID_C and dt" + index + ".DOT_IDTAG_C = :tagId" + index + " ");
criteriaList.add("dt" + index + ".DOT_ID_C is not null");
sb.append(String.format(" join T_DOCUMENT_TAG dt%d on dt%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dt%d.DOT_IDTAG_C = :tagId%d and dt%d.DOT_DELETEDATE_D is null ", index, index, index, index, index));
parameterMap.put("tagId" + index, tagId);
index++;
}
@@ -228,6 +254,10 @@ public class DocumentDao {
criteriaList.add("d.DOC_LANGUAGE_C = :language");
parameterMap.put("language", criteria.getLanguage());
}
if (criteria.getCreatorId() != null) {
criteriaList.add("d.DOC_IDUSER_C = :creatorId");
parameterMap.put("creatorId", criteria.getCreatorId());
}
criteriaList.add("d.DOC_DELETEDATE_D is null");
@@ -241,7 +271,7 @@ public class DocumentDao {
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
// Assemble results
List<DocumentDto> documentDtoList = new ArrayList<DocumentDto>();
List<DocumentDto> documentDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
DocumentDto documentDto = new DocumentDto();
@@ -251,7 +281,7 @@ public class DocumentDao {
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
documentDto.setFileCount(((Number) o[i]).intValue());
documentDtoList.add(documentDto);
}
@@ -262,9 +292,10 @@ public class DocumentDao {
* Update a document.
*
* @param document Document to update
* @param userId User ID
* @return Updated document
*/
public Document update(Document document) {
public Document update(Document document, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
@@ -275,11 +306,19 @@ public class DocumentDao {
// Update the document
documentFromDb.setTitle(document.getTitle());
documentFromDb.setDescription(document.getDescription());
documentFromDb.setSubject(document.getSubject());
documentFromDb.setIdentifier(document.getIdentifier());
documentFromDb.setPublisher(document.getPublisher());
documentFromDb.setFormat(document.getFormat());
documentFromDb.setSource(document.getSource());
documentFromDb.setType(document.getType());
documentFromDb.setCoverage(document.getCoverage());
documentFromDb.setRights(document.getRights());
documentFromDb.setCreateDate(document.getCreateDate());
documentFromDb.setLanguage(document.getLanguage());
// Create audit log
AuditLogUtil.create(documentFromDb, AuditLogType.UPDATE);
AuditLogUtil.create(documentFromDb, AuditLogType.UPDATE, userId);
return documentFromDb;
}

View File

@@ -23,10 +23,10 @@ public class FileDao {
* Creates a new file.
*
* @param file File
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(File file) {
public String create(File file, String userId) {
// Create the UUID
file.setId(UUID.randomUUID().toString());
@@ -36,7 +36,7 @@ public class FileDao {
em.persist(file);
// Create audit log
AuditLogUtil.create(file, AuditLogType.CREATE);
AuditLogUtil.create(file, AuditLogType.CREATE, userId);
return file.getId();
}
@@ -53,6 +53,20 @@ public class FileDao {
return q.getResultList();
}
/**
* Returns the list of all files from a user.
*
* @param userId User ID
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> findByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.userId = :userId and f.deleteDate is null");
q.setParameter("userId", userId);
return q.getResultList();
}
/**
* Returns an active file.
*
@@ -63,7 +77,11 @@ public class FileDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", id);
return (File) q.getSingleResult();
try {
return (File) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
@@ -78,15 +96,20 @@ public class FileDao {
Query q = em.createQuery("select f from File f where f.id = :id and f.userId = :userId and f.deleteDate is null");
q.setParameter("id", id);
q.setParameter("userId", userId);
return (File) q.getSingleResult();
try {
return (File) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a file.
*
* @param id File ID
* @param userId User ID
*/
public void delete(String id) {
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the file
@@ -99,7 +122,7 @@ public class FileDao {
fileDb.setDeleteDate(dateNow);
// Create audit log
AuditLogUtil.create(fileDb, AuditLogType.DELETE);
AuditLogUtil.create(fileDb, AuditLogType.DELETE, userId);
}
/**
@@ -120,9 +143,7 @@ public class FileDao {
fileFromDb.setDocumentId(file.getDocumentId());
fileFromDb.setContent(file.getContent());
fileFromDb.setOrder(file.getOrder());
// Create audit log
AuditLogUtil.create(fileFromDb, AuditLogType.UPDATE);
fileFromDb.setMimeType(file.getMimeType());
return file;
}
@@ -133,10 +154,12 @@ public class FileDao {
* @param id File ID
* @return File
*/
public File getById(String id) {
public File getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", id);
try {
return em.find(File.class, id);
return (File) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
@@ -145,7 +168,7 @@ public class FileDao {
/**
* Get files by document ID or all orphan files of an user.
*
* @parma userId User ID
* @param userId User ID
* @param documentId Document ID
* @return List of files
*/

View File

@@ -0,0 +1,281 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.UserGroup;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
/**
* Group DAO.
*
* @author bgamard
*/
public class GroupDao {
/**
* Returns a group by name.
*
* @param name Name
* @return Group
*/
public Group getActiveByName(String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select g from Group g where g.name = :name and g.deleteDate is null");
q.setParameter("name", name);
try {
return (Group) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Returns a group by ID.
*
* @param id Group ID
* @return Group
*/
public Group getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", id);
try {
return (Group) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Creates a new group.
*
* @param group Group
* @param userId User ID
* @return New ID
*/
public String create(Group group, String userId) {
// Create the UUID
group.setId(UUID.randomUUID().toString());
// Create the group
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(group);
// Create audit log
AuditLogUtil.create(group, AuditLogType.CREATE, userId);
return group.getId();
}
/**
* Deletes a group.
*
* @param groupId Group ID
* @param userId User ID
*/
public void delete(String groupId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the group
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", groupId);
Group groupDb = (Group) q.getSingleResult();
// Delete the group
Date dateNow = new Date();
groupDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("update UserGroup ug set ug.deleteDate = :dateNow where ug.groupId = :groupId and ug.deleteDate is not null");
q.setParameter("dateNow", dateNow);
q.setParameter("groupId", groupId);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.targetId = :groupId and a.deleteDate is null");
q.setParameter("groupId", groupDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Group g set g.parentId = null where g.parentId = :groupId and g.deleteDate is null");
q.setParameter("groupId", groupDb.getId());
q.executeUpdate();
// Create audit log
AuditLogUtil.create(groupDb, AuditLogType.DELETE, userId);
}
/**
* Add an user to a group.
*
* @param userGroup User group
* @return New ID
*/
public String addMember(UserGroup userGroup) {
// Create the UUID
userGroup.setId(UUID.randomUUID().toString());
// Create the user group
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(userGroup);
return userGroup.getId();
}
/**
* Remove an user from a group.
*
* @param groupId Group ID
* @param userId User ID
*/
public void removeMember(String groupId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user group
Query q = em.createQuery("select ug from UserGroup ug where ug.groupId = :groupId and ug.userId = :userId and ug.deleteDate is null");
q.setParameter("groupId", groupId);
q.setParameter("userId", userId);
UserGroup userGroupDb = (UserGroup) q.getSingleResult();
// Delete the user group
Date dateNow = new Date();
userGroupDb.setDeleteDate(dateNow);
}
/**
* Returns the list of all groups.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of groups
*/
public List<GroupDto> findByCriteria(GroupCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select g.GRP_ID_C as c0, g.GRP_NAME_C as c1, g.GRP_IDPARENT_C as c2, gp.GRP_NAME_C as c3, g.GRP_IDROLE_C ");
if (criteria.getUserId() != null) {
sb.append(" , ug.UGP_ID_C ");
}
sb.append(" from T_GROUP g ");
sb.append(" left join T_GROUP gp on g.GRP_IDPARENT_C = gp.GRP_ID_C ");
// Add search criterias
if (criteria.getSearch() != null) {
criteriaList.add("lower(g.GRP_NAME_C) like lower(:search)");
parameterMap.put("search", "%" + criteria.getSearch() + "%");
}
if (criteria.getUserId() != null) {
// Left join and post-filtering for recursive groups
sb.append(criteria.isRecursive() ? " left " : "");
sb.append(" join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null ");
parameterMap.put("userId", criteria.getUserId());
}
criteriaList.add("g.GRP_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<GroupDto> groupDtoList = new ArrayList<>();
List<GroupDto> userGroupDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
GroupDto groupDto = new GroupDto()
.setId((String) o[i++])
.setName((String) o[i++])
.setParentId((String) o[i++])
.setParentName((String) o[i++])
.setRoleId((String) o[i++]);
groupDtoList.add(groupDto);
if (criteria.getUserId() != null && o[i] != null) {
userGroupDtoList.add(groupDto);
}
}
// Post-query filtering for recursive groups
if (criteria.getUserId() != null && criteria.isRecursive()) {
Set<GroupDto> filteredGroupDtoSet = new HashSet<>();
for (GroupDto userGroupDto : userGroupDtoList) {
filteredGroupDtoSet.add(userGroupDto); // Direct group
findGroupParentHierarchy(filteredGroupDtoSet, groupDtoList, userGroupDto, 0); // Indirect groups
}
groupDtoList = new ArrayList<>(filteredGroupDtoSet);
}
return groupDtoList;
}
/**
* Recursively search group's parents.
*
* @param parentGroupDtoSet Resulting parents
* @param groupDtoList All groups
* @param userGroupDto Reference group to search from
* @param depth Depth
*/
private void findGroupParentHierarchy(Set<GroupDto> parentGroupDtoSet, List<GroupDto> groupDtoList, GroupDto userGroupDto, int depth) {
if (userGroupDto.getParentId() == null || depth == 10) { // Max depth 10 to avoid infinite loop
return;
}
for (GroupDto groupDto : groupDtoList) {
if (groupDto.getId().equals(userGroupDto.getParentId())) {
parentGroupDtoSet.add(groupDto); // Add parent
findGroupParentHierarchy(parentGroupDtoSet, groupDtoList, groupDto, depth + 1); // Find parent's parents
}
}
}
/**
* Update a group.
*
* @param group Group to update
* @param userId User ID
* @return Updated group
*/
public Group update(Group group, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the group
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", group.getId());
Group groupFromDb = (Group) q.getSingleResult();
// Update the group
groupFromDb.setName(group.getName());
groupFromDb.setParentId(group.getParentId());
// Create audit log
AuditLogUtil.create(groupFromDb, AuditLogType.UPDATE, userId);
return groupFromDb;
}
}

View File

@@ -1,43 +0,0 @@
package com.sismics.docs.core.dao.jpa;
import com.sismics.docs.core.model.jpa.Locale;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.List;
/**
* Locale DAO.
*
* @author jtremeaux
*/
public class LocaleDao {
/**
* Gets a locale by its ID.
*
* @param id Locale ID
* @return Locale
*/
public Locale getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(Locale.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Returns the list of all locales.
*
* @return List of locales
*/
@SuppressWarnings("unchecked")
public List<Locale> findAll() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select l from Locale l order by l.id");
return q.getResultList();
}
}

View File

@@ -0,0 +1,99 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.dao.jpa.dto.RelationDto;
import com.sismics.docs.core.model.jpa.Relation;
import com.sismics.util.context.ThreadLocalContext;
/**
* Relation DAO.
*
* @author bgamard
*/
public class RelationDao {
/**
* Get all relations from/to a document.
*
* @param documentId Document ID
* @return List of relations
*/
@SuppressWarnings("unchecked")
public List<RelationDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, r.REL_IDDOCFROM_C ");
sb.append(" from T_RELATION r ");
sb.append(" join T_DOCUMENT d on d.DOC_ID_C = r.REL_IDDOCFROM_C and r.REL_IDDOCFROM_C != :documentId or d.DOC_ID_C = r.REL_IDDOCTO_C and r.REL_IDDOCTO_C != :documentId ");
sb.append(" where (r.REL_IDDOCFROM_C = :documentId or r.REL_IDDOCTO_C = :documentId) ");
sb.append(" and r.REL_DELETEDATE_D is null ");
sb.append(" order by d.DOC_TITLE_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<RelationDto> relationDtoList = new ArrayList<RelationDto>();
for (Object[] o : l) {
int i = 0;
RelationDto relationDto = new RelationDto();
relationDto.setId((String) o[i++]);
relationDto.setTitle((String) o[i++]);
String fromDocId = (String) o[i++];
relationDto.setSource(documentId.equals(fromDocId));
relationDtoList.add(relationDto);
}
return relationDtoList;
}
/**
* Update relations on a document.
*
* @param documentId Document ID
* @param documentIdSet Set of document ID
*/
public void updateRelationList(String documentId, Set<String> documentIdSet) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get current relations from this document
Query q = em.createQuery("select r from Relation r where r.fromDocumentId = :documentId and r.deleteDate is null");
q.setParameter("documentId", documentId);
@SuppressWarnings("unchecked")
List<Relation> relationList = q.getResultList();
// Deleting relations no longer there
for (Relation relation : relationList) {
if (!documentIdSet.contains(relation.getToDocumentId())) {
relation.setDeleteDate(new Date());
}
}
// Adding new relations
for (String targetDocId : documentIdSet) {
boolean found = false;
for (Relation relation : relationList) {
if (relation.getToDocumentId().equals(targetDocId)) {
found = true;
break;
}
}
if (!found) {
Relation relation = new Relation();
relation.setId(UUID.randomUUID().toString());
relation.setFromDocumentId(documentId);
relation.setToDocumentId(targetDocId);
em.persist(relation);
}
}
}
}

View File

@@ -16,17 +16,17 @@ public class RoleBaseFunctionDao {
/**
* Find the set of base functions of a role.
*
* @param roleId Role ID
* @param roleIdSet Set of role ID
* @return Set of base functions
*/
@SuppressWarnings("unchecked")
public Set<String> findByRoleId(String roleId) {
public Set<String> findByRoleId(Set<String> roleIdSet) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select rbf.RBF_IDBASEFUNCTION_C from T_ROLE_BASE_FUNCTION rbf, T_ROLE r");
sb.append(" where rbf.RBF_IDROLE_C = :roleId and rbf.RBF_DELETEDATE_D is null");
sb.append(" where rbf.RBF_IDROLE_C in (:roleIdSet) and rbf.RBF_DELETEDATE_D is null");
sb.append(" and r.ROL_ID_C = rbf.RBF_IDROLE_C and r.ROL_DELETEDATE_D is null");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("roleId", roleId);
q.setParameter("roleIdSet", roleIdSet);
return Sets.newHashSet(q.getResultList());
}
}

View File

@@ -2,7 +2,9 @@ package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -10,12 +12,16 @@ import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
import com.sismics.docs.core.model.jpa.DocumentTag;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
/**
@@ -39,19 +45,6 @@ public class TagDao {
}
}
/**
* Returns the list of all tags.
*
* @return List of tags
*/
@SuppressWarnings("unchecked")
public List<Tag> getByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.userId = :userId and t.deleteDate is null order by t.name");
q.setParameter("userId", userId);
return q.getResultList();
}
/**
* Update tags on a document.
*
@@ -94,84 +87,14 @@ public class TagDao {
}
}
/**
* Returns tag list on a document.
*
* @param documentId Document ID
* @return List of tags
*/
@SuppressWarnings("unchecked")
public List<TagDto> getByDocumentId(String documentId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C from T_DOCUMENT_TAG dt ");
sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C ");
sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null ");
sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null ");
sb.append(" order by t.TAG_NAME_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
q.setParameter("userId", userId);
List<Object[]> l = q.getResultList();
// Assemble results
List<TagDto> tagDtoList = new ArrayList<TagDto>();
for (Object[] o : l) {
int i = 0;
TagDto tagDto = new TagDto();
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
tagDtoList.add(tagDto);
}
return tagDtoList;
}
/**
* Returns stats on tags.
*
* @param documentId Document ID
* @return Stats by tag
*/
@SuppressWarnings("unchecked")
public List<TagStatDto> getStats(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, count(d.DOC_ID_C) ");
sb.append(" from T_TAG t ");
sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C and dt.DOT_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = dt.DOT_IDDOCUMENT_C and d.DOC_DELETEDATE_D is null and d.DOC_IDUSER_C = :userId ");
sb.append(" where t.TAG_IDUSER_C = :userId and t.TAG_DELETEDATE_D is null ");
sb.append(" group by t.TAG_ID_C ");
sb.append(" order by t.TAG_NAME_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("userId", userId);
List<Object[]> l = q.getResultList();
// Assemble results
List<TagStatDto> tagStatDtoList = new ArrayList<TagStatDto>();
for (Object[] o : l) {
int i = 0;
TagStatDto tagDto = new TagStatDto();
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
tagDto.setCount(((Number) o[i++]).intValue());
tagStatDtoList.add(tagDto);
}
return tagStatDtoList;
}
/**
* Creates a new tag.
*
* @param tag Tag
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Tag tag) {
public String create(Tag tag, String userId) {
// Create the UUID
tag.setId(UUID.randomUUID().toString());
@@ -181,55 +104,18 @@ public class TagDao {
em.persist(tag);
// Create audit log
AuditLogUtil.create(tag, AuditLogType.CREATE);
AuditLogUtil.create(tag, AuditLogType.CREATE, userId);
return tag.getId();
}
/**
* Returns a tag by name.
*
* @param userId User ID
* @param name Name
* @return Tag
*/
public Tag getByName(String userId, String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.name = :name and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("name", name);
try {
return (Tag) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Returns a tag by ID.
*
* @param userId User ID
* @param tagId Tag ID
* @return Tag
*/
public Tag getByTagId(String userId, String tagId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.id = :tagId and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("tagId", tagId);
try {
return (Tag) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a tag.
*
* @param tagId Tag ID
* @param userId User ID
*/
public void delete(String tagId) {
public void delete(String tagId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the tag
@@ -238,39 +124,32 @@ public class TagDao {
Tag tagDb = (Tag) q.getSingleResult();
// Delete the tag
tagDb.setDeleteDate(new Date());
Date dateNow = new Date();
tagDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("delete DocumentTag dt where dt.tagId = :tagId");
q = em.createQuery("update DocumentTag dt set dt.deleteDate = :dateNow where dt.tagId = :tagId and dt.deleteDate is not null");
q.setParameter("dateNow", dateNow);
q.setParameter("tagId", tagId);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(tagDb, AuditLogType.DELETE);
}
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :tagId and a.deleteDate is null");
q.setParameter("tagId", tagId);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
/**
* Search tags by name.
*
* @param name Tag name
* @return List of found tags
*/
@SuppressWarnings("unchecked")
public List<Tag> findByName(String userId, String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.name like :name and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("name", "%" + name + "%");
return q.getResultList();
// Create audit log
AuditLogUtil.create(tagDb, AuditLogType.DELETE, userId);
}
/**
* Update a tag.
*
* @param tag Tag to update
* @param userId User ID
* @return Updated tag
*/
public Tag update(Tag tag) {
public Tag update(Tag tag, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the tag
@@ -281,10 +160,79 @@ public class TagDao {
// Update the tag
tagFromDb.setName(tag.getName());
tagFromDb.setColor(tag.getColor());
tagFromDb.setParentId(tag.getParentId());
// Create audit log
AuditLogUtil.create(tagFromDb, AuditLogType.UPDATE);
AuditLogUtil.create(tagFromDb, AuditLogType.UPDATE, userId);
return tagFromDb;
}
/**
* Returns the list of all tags.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of groups
*/
public List<TagDto> findByCriteria(TagCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select distinct t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3, u.USE_USERNAME_C as c4 ");
sb.append(" from T_TAG t ");
sb.append(" join T_USER u on t.TAG_IDUSER_C = u.USE_ID_C ");
// Add search criterias
if (criteria.getId() != null) {
criteriaList.add("t.TAG_ID_C = :id");
parameterMap.put("id", criteria.getId());
}
if (criteria.getTargetIdList() != null) {
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = t.TAG_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
criteriaList.add("a.ACL_ID_C is not null");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (criteria.getDocumentId() != null) {
sb.append(" join T_DOCUMENT_TAG dt on dt.DOT_IDTAG_C = t.TAG_ID_C and dt.DOT_DELETEDATE_D is null ");
criteriaList.add("dt.DOT_IDDOCUMENT_C = :documentId");
parameterMap.put("documentId", criteria.getDocumentId());
}
if (criteria.getName() != null) {
criteriaList.add("t.TAG_NAME_C = :name");
parameterMap.put("name", criteria.getName());
}
if (criteria.getNameLike() != null) {
criteriaList.add("t.TAG_NAME_C like :nameLike");
parameterMap.put("nameLike", "%" + criteria.getNameLike() + "%");
}
criteriaList.add("t.TAG_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<TagDto> tagDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
TagDto tagDto = new TagDto()
.setId((String) o[i++])
.setName((String) o[i++])
.setColor((String) o[i++])
.setParentId((String) o[i++])
.setCreator((String) o[i]);
tagDtoList.add(tagDto);
}
return tagDtoList;
}
}

View File

@@ -16,14 +16,12 @@ import org.mindrot.jbcrypt.BCrypt;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
@@ -38,9 +36,9 @@ public class UserDao {
*
* @param username User login
* @param password User password
* @return ID of the authenticated user or null
* @return The authenticated user or null
*/
public String authenticate(String username, String password) {
public User authenticate(String username, String password) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
q.setParameter("username", username);
@@ -49,7 +47,7 @@ public class UserDao {
if (!BCrypt.checkpw(password, user.getPassword())) {
return null;
}
return user.getId();
return user;
} catch (NoResultException e) {
return null;
}
@@ -59,10 +57,11 @@ public class UserDao {
* Creates a new user.
*
* @param user User to create
* @param userId User ID
* @return User ID
* @throws Exception
*/
public String create(User user) throws Exception {
public String create(User user, String userId) throws Exception {
// Create the user UUID
user.setId(UUID.randomUUID().toString());
@@ -78,11 +77,10 @@ public class UserDao {
// Create the user
user.setCreateDate(new Date());
user.setPassword(hashPassword(user.getPassword()));
user.setTheme(Constants.DEFAULT_THEME_ID);
em.persist(user);
// Create audit log
AuditLogUtil.create(user, AuditLogType.CREATE);
AuditLogUtil.create(user, AuditLogType.CREATE, userId);
return user.getId();
}
@@ -91,9 +89,36 @@ public class UserDao {
* Updates a user.
*
* @param user User to update
* @param userId User ID
* @return Updated user
*/
public User update(User user) {
public User update(User user, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userFromDb = (User) q.getSingleResult();
// Update the user (except password)
userFromDb.setEmail(user.getEmail());
userFromDb.setStorageQuota(user.getStorageQuota());
userFromDb.setStorageCurrent(user.getStorageCurrent());
userFromDb.setTotpKey(user.getTotpKey());
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
return user;
}
/**
* Updates a user's quota.
*
* @param user User to update
* @return Updated user
*/
public User updateQuota(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
@@ -102,13 +127,7 @@ public class UserDao {
User userFromDb = (User) q.getSingleResult();
// Update the user
userFromDb.setLocaleId(user.getLocaleId());
userFromDb.setEmail(user.getEmail());
userFromDb.setTheme(user.getTheme());
userFromDb.setFirstConnection(user.isFirstConnection());
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE);
userFromDb.setStorageQuota(user.getStorageQuota());
return user;
}
@@ -117,9 +136,10 @@ public class UserDao {
* Update the user password.
*
* @param user User to update
* @param userId User ID
* @return Updated user
*/
public User updatePassword(User user) {
public User updatePassword(User user, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
@@ -131,7 +151,7 @@ public class UserDao {
userFromDb.setPassword(hashPassword(user.getPassword()));
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE);
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
return user;
}
@@ -168,29 +188,13 @@ public class UserDao {
}
}
/**
* Gets an active user by its password recovery token.
*
* @param passwordResetKey Password recovery token
* @return User
*/
public User getActiveByPasswordResetKey(String passwordResetKey) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select u from User u where u.passwordResetKey = :passwordResetKey and u.deleteDate is null");
q.setParameter("passwordResetKey", passwordResetKey);
return (User) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a user.
*
* @param username User's username
* @param userId User ID
*/
public void delete(String username) {
public void delete(String username, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
@@ -207,8 +211,28 @@ public class UserDao {
q.setParameter("userId", userFromDb.getId());
q.executeUpdate();
q = em.createQuery("update Document d set d.deleteDate = :dateNow where d.userId = :userId and d.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update File f set f.deleteDate = :dateNow where f.userId = :userId and f.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.targetId = :userId and a.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Comment c set c.deleteDate = :dateNow where c.userId = :userId and c.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.DELETE);
AuditLogUtil.create(userFromDb, AuditLogType.DELETE, userId);
}
/**
@@ -217,21 +241,22 @@ public class UserDao {
* @param password Clear password
* @return Hashed password
*/
protected String hashPassword(String password) {
private String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
/**
* Returns the list of all users.
*
* @param paginatedList List of users (updated by side effects)
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of users
*/
public void findByCriteria(PaginatedList<UserDto> paginatedList, UserCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
public List<UserDto> findByCriteria(UserCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_IDLOCALE_C as c4");
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5");
sb.append(" from T_USER u ");
// Add search criterias
@@ -240,6 +265,11 @@ public class UserDao {
parameterMap.put("search", "%" + criteria.getSearch() + "%");
}
if (criteria.getGroupId() != null) {
sb.append(" join T_USER_GROUP ug on ug.UGP_IDUSER_C = u.USE_ID_C and ug.UGP_IDGROUP_C = :groupId and ug.UGP_DELETEDATE_D is null ");
parameterMap.put("groupId", criteria.getGroupId());
}
criteriaList.add("u.USE_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
@@ -248,11 +278,12 @@ public class UserDao {
}
// Perform the search
QueryParam queryParam = new QueryParam(sb.toString(), parameterMap);
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<UserDto> userDtoList = new ArrayList<UserDto>();
List<UserDto> userDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
UserDto userDto = new UserDto();
@@ -260,9 +291,10 @@ public class UserDao {
userDto.setUsername((String) o[i++]);
userDto.setEmail((String) o[i++]);
userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
userDto.setLocaleId((String) o[i++]);
userDto.setStorageCurrent(((Number) o[i++]).longValue());
userDto.setStorageQuota(((Number) o[i]).longValue());
userDtoList.add(userDto);
}
paginatedList.setResultList(userDtoList);
return userDtoList;
}
}

View File

@@ -0,0 +1,104 @@
package com.sismics.docs.core.dao.jpa;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.sismics.docs.core.model.jpa.Vocabulary;
import com.sismics.util.context.ThreadLocalContext;
/**
* Vocabulary DAO.
*
* @author bgamard
*/
public class VocabularyDao {
/**
* Creates a new vocabulary entry.
*
* @param vocabulary Vocabulary
* @return New ID
* @throws Exception
*/
public String create(Vocabulary vocabulary) {
// Create the UUID
vocabulary.setId(UUID.randomUUID().toString());
// Create the comment
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(vocabulary);
return vocabulary.getId();
}
/**
* Get all vocabulary entries sharing a single name.
*
* @param name Name
* @return Vocabulary entries
*/
@SuppressWarnings("unchecked")
public List<Vocabulary> getByName(String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the entries
Query q = em.createQuery("select v from Vocabulary v where v.name = :name order by v.order");
q.setParameter("name", name);
return q.getResultList();
}
/**
* Get a vocabulary entry by ID.
*
* @param id ID
* @return Vocabulary
*/
public Vocabulary getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(Vocabulary.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Update a vocabulary entry.
*
* @param vocabulary Vocabulary to update
*/
public Vocabulary update(Vocabulary vocabulary) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the vocabulary entry
Query q = em.createQuery("select v from Vocabulary v where v.id = :id");
q.setParameter("id", vocabulary.getId());
Vocabulary vocabularyFromDb = (Vocabulary) q.getSingleResult();
// Update the vocabulary entry
vocabularyFromDb.setName(vocabulary.getName());
vocabularyFromDb.setValue(vocabulary.getValue());
vocabularyFromDb.setOrder(vocabulary.getOrder());
return vocabularyFromDb;
}
/**
* Deletes a vocabulary entry.
*
* @param id Vocabulary ID
*/
public void delete(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the vocabulary
Query q = em.createQuery("select v from Vocabulary v where v.id = :id");
q.setParameter("id", id);
Vocabulary vocabularyDb = (Vocabulary) q.getSingleResult();
em.remove(vocabularyDb);
}
}

View File

@@ -11,9 +11,9 @@ import java.util.List;
*/
public class DocumentCriteria {
/**
* User ID.
* ACL target ID list.
*/
private String userId;
private List<String> targetIdList;
/**
* Search query.
@@ -51,146 +51,79 @@ public class DocumentCriteria {
private String language;
/**
* Getter of userId.
*
* @return userId
* Creator ID.
*/
public String getUserId() {
return userId;
private String creatorId;
public List<String> getTargetIdList() {
return targetIdList;
}
/**
* Setter of userId.
*
* @param userId userId
*/
public void setUserId(String userId) {
this.userId = userId;
public void setTargetIdList(List<String> targetIdList) {
this.targetIdList = targetIdList;
}
/**
* Getter of search.
*
* @return the search
*/
public String getSearch() {
return search;
}
/**
* Setter of search.
*
* @param search search
*/
public void setSearch(String search) {
this.search = search;
}
/**
* Getter of fullSearch.
*
* @return the fullSearch
*/
public String getFullSearch() {
return fullSearch;
}
/**
* Setter of fullSearch.
*
* @param fullSearch fullSearch
*/
public void setFullSearch(String fullSearch) {
this.fullSearch = fullSearch;
}
/**
* Getter of createDateMin.
*
* @return the createDateMin
*/
public Date getCreateDateMin() {
return createDateMin;
}
/**
* Setter of createDateMin.
*
* @param createDateMin createDateMin
*/
public void setCreateDateMin(Date createDateMin) {
this.createDateMin = createDateMin;
}
/**
* Getter of createDateMax.
*
* @return the createDateMax
*/
public Date getCreateDateMax() {
return createDateMax;
}
/**
* Setter of createDateMax.
*
* @param createDateMax createDateMax
*/
public void setCreateDateMax(Date createDateMax) {
this.createDateMax = createDateMax;
}
/**
* Getter of tagIdList.
*
* @return the tagIdList
*/
public List<String> getTagIdList() {
return tagIdList;
}
/**
* Setter of tagIdList.
*
* @param tagIdList tagIdList
*/
public void setTagIdList(List<String> tagIdList) {
this.tagIdList = tagIdList;
}
/**
* Getter of shared.
*
* @return the shared
*/
public Boolean getShared() {
return shared;
}
/**
* Setter of shared.
*
* @param shared shared
*/
public void setShared(Boolean shared) {
this.shared = shared;
}
/**
* Getter of language.
*
* @return the language
*/
public String getLanguage() {
return language;
}
/**
* Setter of language.
*
* @param language language
*/
public void setLanguage(String language) {
this.language = language;
}
public String getCreatorId() {
return creatorId;
}
public void setCreatorId(String creatorId) {
this.creatorId = creatorId;
}
}

View File

@@ -0,0 +1,50 @@
package com.sismics.docs.core.dao.jpa.criteria;
/**
* Group criteria.
*
* @author bgamard
*/
public class GroupCriteria {
/**
* Search query.
*/
private String search;
/**
* User ID.
*/
private String userId;
/**
* Retrieve user groups recursively.
*/
private boolean recursive = false;
public String getSearch() {
return search;
}
public GroupCriteria setSearch(String search) {
this.search = search;
return this;
}
public String getUserId() {
return userId;
}
public GroupCriteria setUserId(String userId) {
this.userId = userId;
return this;
}
public boolean isRecursive() {
return recursive;
}
public GroupCriteria setRecursive(boolean recursive) {
this.recursive = recursive;
return this;
}
}

View File

@@ -0,0 +1,80 @@
package com.sismics.docs.core.dao.jpa.criteria;
import java.util.List;
/**
* Tag criteria.
*
* @author bgamard
*/
public class TagCriteria {
/**
* Tag ID.
*/
private String id;
/**
* ACL target ID list.
*/
private List<String> targetIdList;
/**
* Document ID.
*/
private String documentId;
/**
* Tag name.
*/
private String name;
/**
* Approximate tag name.
*/
private String nameLike;
public String getId() {
return id;
}
public TagCriteria setId(String id) {
this.id = id;
return this;
}
public List<String> getTargetIdList() {
return targetIdList;
}
public TagCriteria setTargetIdList(List<String> targetIdList) {
this.targetIdList = targetIdList;
return this;
}
public String getDocumentId() {
return documentId;
}
public TagCriteria setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String getName() {
return name;
}
public TagCriteria setName(String name) {
this.name = name;
return this;
}
public String getNameLike() {
return nameLike;
}
public TagCriteria setNameLike(String nameLike) {
this.nameLike = nameLike;
return this;
}
}

View File

@@ -1,7 +1,5 @@
package com.sismics.docs.core.dao.jpa.criteria;
/**
* User criteria.
*
@@ -14,21 +12,25 @@ public class UserCriteria {
private String search;
/**
* Getter of search.
*
* @return the search
* Group ID.
*/
private String groupId;
public String getSearch() {
return search;
}
/**
* Setter of search.
*
* @param search search
*/
public UserCriteria setSearch(String search) {
this.search = search;
return this;
}
public String getGroupId() {
return groupId;
}
public UserCriteria setGroupId(String groupId) {
this.groupId = groupId;
return this;
}
}

View File

@@ -1,7 +1,5 @@
package com.sismics.docs.core.dao.jpa.dto;
import javax.persistence.Id;
import com.sismics.docs.core.constant.PermType;
/**
@@ -13,7 +11,6 @@ public class AclDto {
/**
* Acl ID.
*/
@Id
private String id;
/**

Some files were not shown because too many files have changed in this diff Show More