1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-07 16:06:08 +00:00

Compare commits

...

326 Commits

Author SHA1 Message Date
Aitorbp
71224c30d1
Merge pull request #581 from owncloud/release/4.1
[RELEASE] 4.1 library to master
2023-09-05 09:49:55 +01:00
Aitorbp
c82d75b1d8 New fix for snackbar when copy without server on 2023-08-30 10:16:58 +01:00
Manuel Plazas Palacio
530999848f Solving bugs when getting file existence in OC10 2023-08-24 14:12:30 +02:00
Manuel Plazas Palacio
b4625d017c Solving bugs when getting file existence. 2023-08-24 12:28:23 +02:00
Manuel Plazas Palacio
2c698cc2a4
Merge pull request #578 from owncloud/feature/unneccesary_call
[BUG] unneccessary or wrong call ....
2023-08-23 10:56:22 +02:00
Manuel Plazas Palacio
bf183fe04d Solving bug when copyin or moving 2023-08-22 15:24:59 +02:00
Manuel Plazas Palacio
1f8de383b6 Removing extra path on check path existence. 2023-08-18 11:14:11 +02:00
Aitorbp
8eb435a1c2
Merge pull request #577 from owncloud/feature/respect_app_provider_values_from_capabilities
[FEATURE REQUEST] Respect app_provider values from capabilities
2023-08-03 08:26:26 +01:00
Aitorbp
052566d205 Added variable from local data source 2023-07-31 12:43:16 +01:00
Manuel Plazas Palacio
063c3fa9e9
Merge pull request #571 from owncloud/feature/copy_move_conflic_solved_users
[FEATURE REQUEST] Copy/move conflict solved by users
2023-06-21 08:33:47 +02:00
Manuel Plazas Palacio
fa143804d2 Solving CR changes. 2023-06-08 13:31:09 +02:00
Manuel Plazas Palacio
5b64876e2c Showing decision dialog when moving file conflict.
Solving errors when copying, replace and keep both.
2023-06-07 12:15:14 +02:00
Manuel Plazas Palacio
d81ca97f14 Showing decision dialog when copying file conflict. 2023-06-07 12:15:14 +02:00
Juan Carlos Garrote
5607f76a1d
Merge pull request #566 from owncloud/technical/min_sdk_23
[TECHNICAL] Upgrade min SDK to Android 6 (v23)
2023-05-25 14:03:37 +02:00
Juan Carlos Garrote
b4137502d2 Remove unnecessary code after upgrading to min SDK 23 2023-05-25 13:33:49 +02:00
Juan Carlos Garrote
261075a8ad Update minSdkVersion to 23 (Android 6) 2023-05-25 13:33:49 +02:00
Juan Carlos Garrote
c2874357f0
Merge pull request #569 from owncloud/release/4.0
[Release] 2.1
2023-05-25 13:30:31 +02:00
Juan Carlos Garrote
e6937b4210 Removed publicUpload parameter from public shares creation and update requests 2023-05-15 14:01:16 +02:00
Juan Carlos Garrote
89dd13cec4
Merge pull request #562 from owncloud/feature/create_file_web
[FEATURE REQUEST] Create new file via Open in Web
2023-05-04 09:11:24 +02:00
Juan Carlos Garrote
ab3a594e5c Create network call to create file via app provider 2023-04-14 11:02:21 +02:00
Juan Carlos Garrote
cbbe044212
Merge pull request #558 from owncloud/fix/unknown_error_message
[FIX] Introduce new error message for ProtocolException
2023-04-13 08:26:42 +02:00
Juan Carlos Garrote
17aa1ea7bd Introduce new error message for ProtocolException 2023-04-13 08:20:50 +02:00
Abel García de Prada
cbf6365e93
Merge pull request #559 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.10
Bump org.robolectric:robolectric from 4.9.2 to 4.10
2023-04-12 08:21:19 +02:00
dependabot[bot]
15bccd3f80
Bump org.robolectric:robolectric from 4.9.2 to 4.10
Bumps [org.robolectric:robolectric](https://github.com/robolectric/robolectric) from 4.9.2 to 4.10.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.9.2...robolectric-4.10)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-12 02:57:14 +00:00
Juan Carlos Garrote
8cee0a37a7
Merge pull request #553 from owncloud/feature/app_registry
[Feature] Open in specific web provider
2023-04-10 09:17:36 +02:00
Abel García de Prada
393ec6e07e Open file in web with a specific app 2023-04-05 16:16:50 +02:00
Abel García de Prada
f35daacdf1 Pleasure the lint 2023-04-05 16:16:50 +02:00
Abel García de Prada
2ac5cf0657 Move open in web operations to their proper location 2023-04-05 16:16:50 +02:00
Abel García de Prada
173b12eeca Retrieve the list of available apps to open in web 2023-04-05 16:16:50 +02:00
Juan Carlos Garrote
602ab41fcc
Merge pull request #555 from owncloud/feature/auth_webfinger_flow
Update WebFinger flow
2023-04-04 13:44:31 +02:00
Juan Carlos Garrote
3a27baad3a Fix WebfingerResponse tests 2023-04-04 11:51:06 +02:00
Abel García de Prada
14baadd7ea Webfinger calls won't follow redirections. Only working with 2XX 2023-03-31 09:41:57 +02:00
Abel García de Prada
fa65c1a84d
Merge pull request #551 from owncloud/feature/add_language_header
Add accept language header to all requests
2023-03-24 13:33:42 +01:00
Abel García de Prada
c429b575a9 Add accept language header to all requests 2023-03-24 08:31:34 +01:00
Abel García de Prada
f7d4d27ebb
Merge pull request #552 from owncloud/remove_drives_permission_parsing
Remove permission parsing from spaces.
2023-03-23 10:21:01 +01:00
Abel García de Prada
3681f1001a Remove permission parsing from spaces. Will be done via WebDav permissions 2023-03-22 15:23:57 +01:00
Abel García de Prada
ee5130d3e6
Merge pull request #550 from owncloud/technical/bump_target_sdk
[TECHNICAL] Bump target sdk to 33
2023-03-21 12:23:38 +01:00
Abel García de Prada
1f93cfaf52 Bump target sdk to 33 2023-03-16 13:06:00 +01:00
Juan Carlos Garrote
e6e30fc352
Merge pull request #549 from owncloud/release/2.1
[Release] 2.1 beta.1
2023-03-13 13:09:04 +01:00
Abel García de Prada
c53475da37 Removing quotas from webdav properties in regular propfinds 2023-03-10 12:47:35 +01:00
Abel García de Prada
c2f32b2a82
Merge pull request #548 from owncloud/technical/replace_kapt_with_ksp
[Technical] Replace kapt with ksp
2023-03-09 13:02:47 +01:00
Abel García de Prada
6846e25fa3 Replace kapt with ksp 2023-03-09 11:51:13 +01:00
Juan Carlos Garrote
0e9f9c6391
Merge pull request #530 from owncloud/spaces/main
[SPACES] Main features
2023-03-09 11:49:00 +01:00
Juan Carlos Garrote
159dcd6d68 Rename file to be the same as the class it contains 2023-03-09 09:43:07 +01:00
Juan Carlos Garrote
b660ee98fd Rename files to be the same as the classes they contain 2023-03-09 09:37:09 +01:00
Abel García de Prada
ff90598a2d Rename WebFinger classes to make them consistent 2023-03-09 09:19:25 +01:00
Abel García de Prada
0017079a69 Allow retrieval of several instances from webfinger 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
2458db1828 Fix to KtLint report 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
e6f3fd2e16 Upload workers and network operations adapted to spaces 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
a395787e0b Adapted copy operation for spaces 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
e9f291371d Move network operation adapted to spaces 2023-03-09 09:19:25 +01:00
Abel García de Prada
b65efc2b69 Allow downloads from specific WebDav urls 2023-03-09 09:19:25 +01:00
Abel García de Prada
78665e8cb0 Add support for spaces web dav specific urls to the rename operation 2023-03-09 09:19:25 +01:00
Abel García de Prada
9c844aae7e Support removal of files from specific space 2023-03-09 09:19:25 +01:00
Abel García de Prada
2c18e7b679 Added create folder operation support for specific space 2023-03-09 09:19:25 +01:00
Abel García de Prada
cb73d537e4 Allow support to read specific file from a space 2023-03-09 09:19:25 +01:00
Abel García de Prada
8e4f243031 Fix remote path retrieval. Now it depends on webdav url to support spaces 2023-03-09 09:19:25 +01:00
Abel García de Prada
a65a82cae0 Adapt the propfind to work with specific webdavurl from the space 2023-03-09 09:19:25 +01:00
Abel García de Prada
45f53656ba Update permissions parsing to latest api changes
Supports api renaming from grantedTo to grantedToIdentities on v1.0.1
https://github.com/owncloud/libre-graph-api/releases/tag/v1.0.1
2023-03-09 09:19:25 +01:00
Abel García de Prada
8a4fcb6550 Support shares space 2023-03-09 09:19:25 +01:00
Abel García de Prada
31ccf56e97 Fix parsing when space is disabled 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
0d7a49b3ff Some type changes and renamings 2023-03-09 09:19:25 +01:00
Abel García de Prada
7bb94cf289 Make quota attribute nullable. Its not mandatory 2023-03-09 09:19:25 +01:00
Abel García de Prada
b1a3402656 Create spaces fetch operation and spaces service 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
f0dda9eb8b Added trailing comma 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
4f001550f9 Save spaces-related capabilities in database 2023-03-09 09:19:25 +01:00
Juan Carlos Garrote
e30246c486
Merge pull request #547 from owncloud/release/2.0.3
[Release] 2.0.3
2023-03-07 18:20:51 +01:00
Abel García de Prada
602b7e7548 Show the url in the response http log too 2023-03-07 14:14:54 +01:00
Abel García de Prada
f8da84d0ad
Merge pull request #544 from owncloud/dependabot/gradle/com.android.tools.build-gradle-7.4.2
Bump com.android.tools.build:gradle from 7.4.1 to 7.4.2
2023-02-28 08:02:23 +01:00
dependabot[bot]
763005b051
Bump com.android.tools.build:gradle from 7.4.1 to 7.4.2
Bumps com.android.tools.build:gradle from 7.4.1 to 7.4.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-28 02:57:18 +00:00
Abel García de Prada
d6f969bd91
Merge pull request #540 from owncloud/bump/kotlin_gradle
Bump kotlin version to 1.8.10
2023-02-14 10:17:37 +01:00
Abel García de Prada
4200ed324c Bump kotlin version to 1.8.10 2023-02-14 09:45:29 +01:00
Abel García de Prada
7ee2c5eb15
Merge pull request #535 from owncloud/dependabot/gradle/org.jlleitschuh.gradle-ktlint-gradle-11.1.0
Bump ktlint-gradle from 11.0.0 to 11.1.0
2023-01-30 10:47:27 +01:00
dependabot[bot]
b5cd4a1df2
Bump ktlint-gradle from 11.0.0 to 11.1.0
Bumps ktlint-gradle from 11.0.0 to 11.1.0.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle:ktlint-gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 02:11:34 +00:00
Abel García de Prada
8a8a931e66
Merge pull request #534 from owncloud/release/2.0.2
[Release] 2.0.2
2023-01-27 13:11:39 +01:00
Abel García de Prada
148a97cd32 Potential fix to oauth error after logging in for first time that makes user to reauthenticate 2023-01-26 14:36:38 +01:00
Abel García de Prada
00948ffd73
Merge pull request #528 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.9.2
Bump robolectric from 4.9.1 to 4.9.2
2023-01-09 08:34:18 +01:00
dependabot[bot]
118646290d
Bump robolectric from 4.9.1 to 4.9.2
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.9.1 to 4.9.2.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.9.1...robolectric-4.9.2)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-28 02:01:23 +00:00
Abel García de Prada
defa4d1469
Merge pull request #525 from owncloud/debug/connection_validator_logs
Add several logs to try to debug potential errors related to token refreshment
2022-12-21 12:15:35 +01:00
Abel García de Prada
3545686a31 Add several logs to try to debug potential errors related to oAuth 2022-12-21 11:32:55 +01:00
Abel García de Prada
b710070426
Merge pull request #524 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.9.1
Bump robolectric from 4.9 to 4.9.1
2022-12-20 12:58:27 +01:00
dependabot[bot]
a0750c613a
Bump robolectric from 4.9 to 4.9.1
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.9 to 4.9.1.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.9...robolectric-4.9.1)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-20 02:01:28 +00:00
Abel García de Prada
9df62a51f3
Merge pull request #523 from owncloud/fix/unshare_ocis
Unsharing wont return anything anymore since result object was not used
2022-12-19 08:29:11 +01:00
Abel García de Prada
fa7bfdd597 Unsharing wont return anything anymore since result object was not used 2022-12-15 15:26:37 +01:00
Abel García de Prada
cb076aa6b9
Merge pull request #520 from owncloud/dependencies/bump_bulk
Bump several dependencies at the same time
2022-12-15 14:49:39 +01:00
Abel García de Prada
7ceef58382 Bump gradle plugin to 7.3.1 and move package name fro manifest to gradle namespace 2022-12-13 19:00:10 +01:00
Abel García de Prada
a877612fca Bump several dependencies at the same time 2022-12-13 14:51:57 +01:00
Abel García de Prada
359e591275
Merge pull request #339 from owncloud/new_arch/synchronization
[New arch] Synchronization
2022-12-12 09:54:27 +01:00
Abel García de Prada
f9bc792ded Do data field not mandatory on ocs response 2022-11-28 19:35:47 +01:00
Juan Carlos Garrote
9adadbddcc Fix lint reports 2022-11-28 19:35:47 +01:00
Abel García de Prada
1ecb8020b1 Remove legacy KEY_OC_VERSION constant 2022-11-28 19:35:47 +01:00
Abel García de Prada
b7d3cc2687 Remove legacy owncloud version from the client. 2022-11-28 19:35:47 +01:00
Juan Carlos Garrote
2b27b9657c Increased the chunk size to 10 MB 2022-11-28 19:35:47 +01:00
Abel García de Prada
959cb7b015 Retrieve Etag from successful upload 2022-11-28 19:35:47 +01:00
Abel García de Prada
b59a4e6947 Read remote file function added to the file service 2022-11-28 19:35:47 +01:00
Abel García de Prada
17821e5760 Migrate ReadRemoteFileOperation to kotlin. Second step to keep git history 2022-11-28 19:35:47 +01:00
Abel García de Prada
2b79175b5a Migrate ReadRemoteFileOperation to kotlin. First step to keep git history 2022-11-28 19:35:47 +01:00
Abel García de Prada
b25fbc4604 Fix an error after rebasing with latest version 2022-11-28 19:35:47 +01:00
Abel García de Prada
79173af930 Polish a little bit the code 2022-11-28 19:35:47 +01:00
Abel García de Prada
c36eedd481 Return Unit when the upload operation succeeds 2022-11-28 19:35:47 +01:00
Abel García de Prada
ba37bce0e1 Add transfer listeners to content uri worker 2022-11-28 19:35:47 +01:00
Abel García de Prada
dc7022c531 Pleasure the ktlint 2022-11-28 19:35:47 +01:00
Abel García de Prada
1f499fb67d Make requireEtag nullable 2022-11-28 19:35:46 +01:00
Abel García de Prada
12040a1261 Migrate old request bodies from java to kotlin 2022-11-28 19:35:46 +01:00
Abel García de Prada
7e56412748 Move content uri request body to a new file 2022-11-28 19:35:46 +01:00
Abel García de Prada
71e9bbd2da Delete old java operations. Use the kotlin ones from now on 2022-11-28 19:35:46 +01:00
Abel García de Prada
cbbeaab251 Migrate remote chunk upload operation to kotlin 2022-11-28 19:35:46 +01:00
Abel García de Prada
340e114a91 Use extension to simplify a little bit the code 2022-11-28 19:35:46 +01:00
Abel García de Prada
c6584d69fd Migrate remote upload operation to kotlin 2022-11-28 19:35:46 +01:00
Abel García de Prada
4c39990edb Add copy operation to the file service 2022-11-28 19:35:46 +01:00
Abel García de Prada
34cc4c87b1 Refactor copy remote file operation. Make overwrite false as default to avoid some potential errors 2022-11-28 19:35:46 +01:00
Abel García de Prada
5449eb7480 Migrate CopyRemoteFileOperation to kotlin. Second step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
33d6141613 Migrate CopyRemoteFileOperation to kotlin. First step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
f7645c1648 Apply code review suggestions 2022-11-28 19:35:46 +01:00
Abel García de Prada
6f33dca672 Remove nullability from a variable 2022-11-28 19:35:46 +01:00
Abel García de Prada
6fb5350dea Add rename file to FileService 2022-11-28 19:35:46 +01:00
Abel García de Prada
24037a7a3e Migrate RenameRemoteFileOperation to kotlin. Second step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
5f01b32b12 Migrate RenameRemoteFileOperation to kotlin. First step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
f849cd76d4 Make overwrite option disabled by default for move operations 2022-11-28 19:35:46 +01:00
Abel García de Prada
7c825b2dc3 Add move operation to file service 2022-11-28 19:35:46 +01:00
Abel García de Prada
b3cccfa007 Add move operation to chunk service 2022-11-28 19:35:46 +01:00
Abel García de Prada
5beb30e07a Migrate MoveRemoteFileOperation to kotlin. Second step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
d736c7cdbf Migrate MoveRemoteFileOperation to kotlin. First step to keep git history 2022-11-28 19:35:46 +01:00
Abel García de Prada
d99444e279 Apply code review suggestions 2022-11-28 19:35:46 +01:00
Abel García de Prada
86f16c3460 Add removeFile to FileService 2022-11-28 19:35:46 +01:00
Abel García de Prada
62dbdb1021 Converting RemoveRemoteFileOperation from java to kotlin. Final step. 2022-11-28 19:35:46 +01:00
Abel García de Prada
f706595a9a Converting RemoveRemoteFileOperation from java to kotlin. Intermediate step to preserve git history. 2022-11-28 19:35:46 +01:00
Abel García de Prada
03303ac4f2 Replace kotlin android extensions with kotlin-parcelize 2022-11-28 19:35:46 +01:00
Abel García de Prada
039245742c Apply code review suggestions 2022-11-28 19:35:46 +01:00
Abel García de Prada
3a996ef583 Download file operation will return unit instead of Any 2022-11-28 19:35:46 +01:00
agarcia
ec71fa6c23 Create parent folder when downloading a file if possible 2022-11-28 19:35:46 +01:00
agarcia
56248af6ec Add download file to file service 2022-11-28 19:35:46 +01:00
agarcia
389c0a0fc6 Migrate DownloadRemoteFileOperation to kotlin 2022-11-28 19:35:46 +01:00
Abel García de Prada
405da9a729 Apply CR changes 2022-11-28 19:35:46 +01:00
Abel García de Prada
a02844d2c7 Map size or length property depending on mimetype 2022-11-28 19:35:46 +01:00
Abel García de Prada
53c99a21c2 Transform RemoteFile to kotlin and apply necessary changes 2022-11-28 19:35:46 +01:00
agarcia
5e478036ae Apply code review suggestions 2022-11-28 19:28:41 +01:00
agarcia
cd94e39b7f Include createFolder as file service operation 2022-11-28 19:28:41 +01:00
agarcia
b0798194be Migrate CreateRemoteFolderOperation to kotlin 2022-11-28 19:28:41 +01:00
agarcia
2b5f80bdb9 Apply CR suggestions 2022-11-28 19:28:41 +01:00
agarcia
32891e2cf5 Add owner field to remote file 2022-11-28 19:28:41 +01:00
agarcia
d164b34fe8 Migrate ReadRemoteFolderOperation to kotlin and add it to FileService 2022-11-28 19:28:41 +01:00
Juan Carlos Garrote
c2f7426a3e
Merge pull request #515 from owncloud/feature/handle_425_response
Handle 425 TOO EARLY propfind responses
2022-11-17 13:15:10 +01:00
Juan Carlos Garrote
1e9d8cef42 Handle 425 when trying to open in web 2022-11-17 12:50:55 +01:00
Abel García de Prada
b083debac9 Handle 425 TOO EARLY propfind responses 2022-11-17 12:50:55 +01:00
Jesús Recio
376d52ac5b
Merge pull request #481 from owncloud/feature/webfinger
add moshi classes for webfinger responses
2022-11-02 11:16:57 +01:00
Abel García de Prada
e769684920 Reformat some webfinger classes 2022-10-13 10:22:39 +02:00
Abel García de Prada
70bf35f683 Remove unused imports 2022-10-11 18:00:03 +02:00
Abel García de Prada
1afa124b7d Add webfinger service and update the remote operation 2022-10-10 13:07:11 +02:00
Christian Schabesberger
f19b2355b4 create scaffold for webfinger request operation 2022-10-06 09:34:13 +02:00
Christian Schabesberger
c966bf14d0 add moshi classes for webfinger responses 2022-10-06 09:34:13 +02:00
Abel García de Prada
2c225d8e66
Merge pull request #509 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.9
Bump robolectric from 4.8.1 to 4.9
2022-10-03 09:32:24 +02:00
dependabot[bot]
f8a829d6b7
Bump robolectric from 4.8.1 to 4.9
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.8.1 to 4.9.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.8.1...robolectric-4.9)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-03 02:15:21 +00:00
Abel García de Prada
0ea053d612
Merge pull request #503 from owncloud/feature/open_in_web
[Feature] Open in web
2022-09-01 10:54:05 +02:00
Abel García de Prada
0be462c9aa Set private link capability to unknown type when it is not retrieved
The CapabilityBooleanType.fromBooleanValue set it to FALSE when the capability is not retrieved. We don't want this for this case. Probably neither for the rest, but changing that now would need deeper testing
2022-08-31 17:21:55 +02:00
Abel García de Prada
32ef5d2125 Add a new capability to allow/disallow private links 2022-08-31 17:21:55 +02:00
Abel García de Prada
6282fbd419 Add the open in web to the service facade 2022-08-31 17:21:55 +02:00
Abel García de Prada
6d235fe74a Add a new remote operation to retrieve the url to open a file with ocis provider 2022-08-31 17:21:55 +02:00
Abel García de Prada
9ab7c139e1 Retrieve the app providers from the capabilities 2022-08-31 17:21:55 +02:00
Abel García de Prada
24b850d20c
Merge pull request #496 from owncloud/propfind_with_shares
Retrieve share type directly within the propfind
2022-07-07 14:59:20 +02:00
Abel García de Prada
5be4eca0f7 Added a new property, so that, we can retrieve the shares within the propfind 2022-07-07 08:18:54 +02:00
Jesús Recio
2dc6f30a5e
Merge pull request #490 from owncloud/fix/eos
Fix unexpected end of stream when connecting with server
2022-05-31 11:48:55 +02:00
Abel García de Prada
f712a384cd Fix unexpected end of stream when connecting with server 2022-05-30 13:41:41 +02:00
Abel García de Prada
1f9bff1df0
Merge pull request #439 from owncloud/Modernize
Modernize
2022-05-30 09:36:35 +02:00
Hannes Achleitner
1412117dcd Android Arctic Fox 2022-05-30 09:12:42 +02:00
Hannes Achleitner
a054adc688 Java 8 is by default 2022-05-30 09:11:56 +02:00
Abel García de Prada
809e641b1a
Merge pull request #488 from owncloud/release/2.21-beta.1
[Release] 1.0.15-beta.1
2022-05-12 09:49:09 +02:00
Fernando Sanz
fd8caa42cb Updated versionCode and versionName 2022-05-11 09:53:47 +02:00
Abel García de Prada
45a9a190f8
Merge pull request #486 from owncloud/bump/kotlin_version
Bump kotlin version to 1.6.21
2022-05-09 14:43:16 +02:00
Abel García de Prada
2931a5494a Bump kotlin version to 1.6.21 2022-05-09 14:09:21 +02:00
Abel García de Prada
35fb64c1bd
Merge pull request #484 from owncloud/dependabot/gradle/org.jlleitschuh.gradle-ktlint-gradle-10.3.0
Bump ktlint-gradle from 10.2.1 to 10.3.0
2022-05-09 14:04:04 +02:00
dependabot[bot]
544fc6efd4
Bump ktlint-gradle from 10.2.1 to 10.3.0
Bumps ktlint-gradle from 10.2.1 to 10.3.0.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle:ktlint-gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 07:13:39 +00:00
Abel García de Prada
16c31988e0
Merge pull request #485 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.8.1
Bump robolectric from 4.8 to 4.8.1
2022-05-04 09:12:31 +02:00
dependabot[bot]
7fe41ce51c
Bump robolectric from 4.8 to 4.8.1
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.8 to 4.8.1.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.8...robolectric-4.8.1)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 02:08:05 +00:00
Abel García de Prada
aaafdb5ea2
Merge pull request #483 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.8
Bump robolectric from 4.7.3 to 4.8
2022-04-29 08:28:09 +02:00
dependabot[bot]
6e503c2cf7
Bump robolectric from 4.7.3 to 4.8
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.7.3 to 4.8.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.7.3...robolectric-4.8)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-29 02:44:52 +00:00
Abel García de Prada
4de236b3c7
Merge pull request #364 from owncloud/fix/okhttp_singleton
remove okhttp singleton
2022-04-27 18:35:58 +02:00
Abel García de Prada
f6eb631fbf Reformat some code 2022-04-25 08:41:14 +02:00
Christian Schabesberger
be758eb5ea apply requested changes 2022-04-25 08:41:14 +02:00
Christian Schabesberger
d16ab2e643 remove sample client yet again 2022-04-25 08:41:14 +02:00
Christian Schabesberger
105830e4f0 apply requested changes 2022-04-25 08:41:14 +02:00
Christian Schabesberger
09375ce053 rebase okhttp_signleton fix on top of connection validator
fix token oauth token refresh error

pleasure the linter
2022-04-25 08:41:14 +02:00
Christian Schabesberger
fc4ae27bbe remove redundant intercept of httpclient with httpmethod 2022-04-25 08:41:14 +02:00
Schabi
344c1e1136 apply required fixes 2022-04-25 08:41:14 +02:00
Schabi
79e4287223 clean up http client 2022-04-25 08:41:14 +02:00
Christian Schabesberger
0313c1e103 get okhttp singleton removal changes to compile 2022-04-25 08:41:14 +02:00
Abel García de Prada
06e361afaa
Merge pull request #479 from owncloud/rename_check_script
rename verify_licnese_script back to check_script
2022-04-22 10:57:49 +02:00
Christian Schabesberger
217216e43f rename verify_licnese_script to check_code_script 2022-04-22 09:44:28 +02:00
Abel García de Prada
060a988978
Merge pull request #478 from owncloud/bump/gradle_kotlin
Bump gradle and kotlin to the latest versions
2022-04-11 09:29:57 +02:00
Abel García de Prada
e0b81bd11a Bump kotlin to 1.6.20 2022-04-08 10:53:23 +02:00
Abel García de Prada
97a613f168 Bump target sdk to api 31 2022-04-08 09:48:29 +02:00
Abel García de Prada
d8ac22d57e
Merge pull request #450 from owncloud/connection_validator
Connection validator
2022-03-31 14:55:31 +02:00
Christian Schabesberger
7c77c267f9 apply changes according to review 2022-03-23 13:27:19 +01:00
Christian Schabesberger
2a4195c966 fix lint and compile issues 2022-03-23 13:27:18 +01:00
Christian Schabesberger
d9dce81ce7 add GetBaseUrlRemoteOperation 2022-03-23 13:26:58 +01:00
Christian Schabesberger
4f3e167efa fix spelling mistakes 2022-03-23 13:26:58 +01:00
Christian Schabesberger
fc8440cc01 remove debug statements 2022-03-23 13:26:58 +01:00
Christian Schabesberger
5ca99a0e69 update base url in active client after 301 redirect
reduce validation retry count
2022-03-23 13:26:58 +01:00
Christian Schabesberger
ce761aaec2 remove redirect code from owncloudclient use okhttp instead 2022-03-23 13:26:58 +01:00
Christian Schabesberger
c59d1540c7 clean up credentials stuff from owncloudclient 2022-03-23 13:26:58 +01:00
Christian Schabesberger
5582097ca1 get access token to update through connection validator
update token
2022-03-23 13:26:58 +01:00
Christian Schabesberger
e27a968ddb add update authToken code to connectionValidator 2022-03-23 13:26:58 +01:00
Christian Schabesberger
cfd69987e9 update session from APM while running the app 2022-03-23 13:26:57 +01:00
Christian Schabesberger
b2f6d7f3b1 make initial check with apm work though connection validator 2022-03-23 13:24:27 +01:00
Christian Schabesberger
7ccb86c153 stop validation process if failing 2022-03-23 13:24:27 +01:00
Christian Schabesberger
e878ad3931 make oidc discovery work again 2022-03-23 13:24:27 +01:00
Schabi
8d09a5c242 make initial connection using connection validator work 2022-03-23 13:24:27 +01:00
Christian Schabesberger
ddb15a33f1 try shifting over to connection validator for updating credentials
intermediate commit
2022-03-23 13:24:27 +01:00
Christian Schabesberger
c2c351c912 set max redirect count for owncloud client to 5 2022-03-23 13:24:27 +01:00
Christian Schabesberger
ca206173df create logic scaffold for connection validator get status.php with it 2022-03-23 13:24:27 +01:00
Christian Schabesberger
7e4b43e7cb prepare code for the inclusion of connection validator 2022-03-23 13:24:27 +01:00
Christian Schabesberger
0e82f983b5 remove OwnCloudClient factory 2022-03-23 13:24:27 +01:00
Christian Schabesberger
0d94058db9 remove retrive cookies from middleware 2022-03-23 13:24:27 +01:00
Christian Schabesberger
2f952a3a09 debug and add mutex for halding requiests 2022-03-23 13:24:27 +01:00
Christian Schabesberger
39b1fce7f6 init connectionvalidator 2022-03-23 13:24:27 +01:00
Abel García de Prada
351efb7bf2
Merge pull request #474 from owncloud/dependabot/gradle/com.facebook.stetho-stetho-okhttp3-1.6.0
Bump stetho-okhttp3 from 1.5.1 to 1.6.0
2022-03-10 11:30:54 +01:00
dependabot[bot]
2edfc17653
Bump stetho-okhttp3 from 1.5.1 to 1.6.0
Bumps [stetho-okhttp3](https://github.com/facebook/stetho) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/facebook/stetho/releases)
- [Changelog](https://github.com/facebook/stetho/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/stetho/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: com.facebook.stetho:stetho-okhttp3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-10 10:15:54 +00:00
Abel García de Prada
e0efa7b3ab
Merge pull request #472 from owncloud/feature/check_script
Feature/check script
2022-03-10 10:21:07 +01:00
Abel García de Prada
5ab6514164 Rename the script and function to clarify their purpose 2022-03-10 09:45:34 +01:00
Christian Schabesberger
194894637e fix wrongly licensed source files 2022-03-10 09:45:34 +01:00
Christian Schabesberger
c662383182 add mit license header to where it was missing 2022-03-10 09:45:34 +01:00
Christian Schabesberger
f8c7c12f5b add check_script 2022-03-10 09:45:34 +01:00
Abel García de Prada
61508c85d1
Merge pull request #469 from owncloud/feature/add_stetho
Feature/add stetho
2022-03-08 09:27:34 +01:00
Christian Schabesberger
5619c1daf8 apply code cleanup according to review 2022-03-08 08:59:10 +01:00
Christian Schabesberger
85a3918ff1 fix usage of debug interceptor 2022-03-08 08:59:10 +01:00
Christian Schabesberger
7e507abf32 add debug interceptor 2022-03-08 08:59:10 +01:00
Abel García de Prada
a7e9138593
Merge pull request #471 from owncloud/fix/remove_sample_client
remove sample client
2022-03-08 08:58:00 +01:00
Christian Schabesberger
0ae5cf5ca2 remove sample client 2022-03-08 08:49:26 +01:00
Abel García de Prada
063e7366e5
Merge pull request #473 from owncloud/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2 to 3
2022-03-07 07:52:38 +01:00
dependabot[bot]
5cec7b43e2
Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 02:03:39 +00:00
Abel García de Prada
ff0cde35fe
Merge pull request #466 from owncloud/release/1.0.14
[Release] 1.0.14
2022-02-16 10:45:45 +01:00
Fernando Sanz
a9a6ca6d4e Upgraded versionName and versionNumber 2022-02-09 14:32:09 +01:00
Abel García de Prada
57882e793e
Merge pull request #464 from owncloud/FixLint
Fix lint warnings
2022-01-31 09:10:32 +01:00
Hannes Achleitner
7365c0b126 Fix lint warnings 2022-01-31 08:09:53 +01:00
Abel García de Prada
5bebbe2d3d
Merge pull request #461 from owncloud/dependabot/gradle/org.jlleitschuh.gradle-ktlint-gradle-10.2.1
Bump ktlint-gradle from 10.2.0 to 10.2.1
2022-01-10 09:23:40 +01:00
dependabot[bot]
3aa9e72a22
Bump ktlint-gradle from 10.2.0 to 10.2.1
Bumps ktlint-gradle from 10.2.0 to 10.2.1.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle:ktlint-gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 02:05:26 +00:00
Abel García de Prada
cc53ed6f68
Merge pull request #460 from owncloud/bump/kotlin_1.6.10
Bump kotlin version to 1.6.10
2021-12-23 10:34:59 +01:00
Abel García de Prada
377572e87a Bump kotlin version to 1.6.10 2021-12-23 09:31:34 +01:00
Abel García de Prada
9fc5ddbc39
Merge pull request #459 from hannesa2/Gradle-Log4j
Bump Gradle for Log4j
2021-12-23 08:37:55 +01:00
Hannes Achleitner
b649ee0ad6 Bump Gradle for Log4j
https://github.com/gradle/gradle/releases/tag/v7.3.3
2021-12-23 08:23:02 +01:00
Abel García de Prada
e6e763882c
Merge pull request #442 from owncloud/feature/avatar_capability_library
[Feature] Respect capability for Avatar support
2021-12-13 11:37:10 +01:00
Fernando Sanz
27e5d367af Ktlint fixes. 2021-12-13 08:53:18 +01:00
Fernando Sanz
2b299da415 Retrieve new capability to user's avatar. 2021-12-13 08:53:18 +01:00
Abel García de Prada
2c0745f20a
Merge pull request #454 from owncloud/dependabot/gradle/moshiVersion-1.13.0
Bump moshiVersion from 1.12.0 to 1.13.0
2021-12-10 08:30:20 +01:00
dependabot[bot]
1a4b98d232
Bump moshiVersion from 1.12.0 to 1.13.0
Bumps `moshiVersion` from 1.12.0 to 1.13.0.

Updates `moshi-kotlin` from 1.12.0 to 1.13.0
- [Release notes](https://github.com/square/moshi/releases)
- [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/moshi/compare/moshi-parent-1.12.0...moshi-parent-1.13.0)

Updates `moshi-kotlin-codegen` from 1.12.0 to 1.13.0
- [Release notes](https://github.com/square/moshi/releases)
- [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/moshi/compare/moshi-parent-1.12.0...moshi-parent-1.13.0)

---
updated-dependencies:
- dependency-name: com.squareup.moshi:moshi-kotlin
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.squareup.moshi:moshi-kotlin-codegen
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-10 02:09:45 +00:00
Abel García de Prada
aef967bf25
Merge pull request #452 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.7.3
Bump robolectric from 4.5.1 to 4.7.3
2021-12-02 08:52:35 +01:00
dependabot[bot]
5ce81e3b17
Bump robolectric from 4.5.1 to 4.7.3
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.5.1 to 4.7.3.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.5.1...robolectric-4.7.3)

---
updated-dependencies:
- dependency-name: org.robolectric:robolectric
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-02 02:08:45 +00:00
Abel García de Prada
4fa986e053
Merge pull request #448 from owncloud/gradle_7.3
Bump gradle version to 7.3
2021-11-17 13:18:31 +01:00
Abel García de Prada
5e8f0b8286 Bump gradle version to 7.3 2021-11-17 10:31:10 +01:00
Abel García de Prada
050b98f78b
Merge pull request #433 from owncloud/Gradle-7.2
Gradle 7.2
2021-11-17 10:03:53 +01:00
Hannes Achleitner
487c4f682a Gradle 7.2 2021-11-17 09:50:05 +01:00
Abel García de Prada
f75f64ab13
Merge pull request #434 from owncloud/api30
Api 30
2021-11-17 09:46:11 +01:00
Hannes Achleitner
dd088d7222 Api 30 2021-11-17 09:32:18 +01:00
Abel García de Prada
05705722ef
Merge pull request #447 from owncloud/bump_several_dependencies
Bump Kotlin version to 1.5.31
2021-11-17 09:19:36 +01:00
Abel García de Prada
36cf45c377 Bump some dependencies 2021-11-17 08:51:39 +01:00
Abel García de Prada
eea5240bd0
Merge pull request #441 from owncloud/release/1.0.13
[Release] 1.0.13
2021-11-15 11:22:22 +01:00
Fernando Sanz
20efe90e2d QA report (5) fixed 2021-11-08 13:37:52 +01:00
Abel García de Prada
83048b61d8 Update versionName and versionCode for a new stable release 2021-11-05 13:18:04 +01:00
Abel García de Prada
8a3ba16f96
Merge pull request #440 from owncloud/release/1.0.13_beta.1
[Release] 1.0.13-beta.1
2021-11-05 08:25:12 +01:00
Abel García de Prada
e6b3df103e Update versionCode and versionName 2021-11-03 18:23:58 +01:00
Abel García de Prada
37bae10618
Merge pull request #436 from owncloud/fix/final_chunk_size
Fix a protocol exception when uploading chunked files
2021-11-03 18:07:18 +01:00
Abel García de Prada
10e44627e1 Change last chunk size to fix a protocol exception when sending files 2021-09-28 10:58:32 +02:00
Abel García de Prada
1baeebe5ee
Merge pull request #430 from owncloud/feature/parse_shares
[FEATURE REQUEST] Use Moshi to parse shares
2021-09-24 11:49:33 +02:00
Abel García de Prada
fa7d83edb3
Merge pull request #432 from owncloud/dependabot/gradle/org.jlleitschuh.gradle-ktlint-gradle-10.2.0
Bump ktlint-gradle from 10.1.0 to 10.2.0
2021-09-09 08:10:44 +02:00
dependabot[bot]
46dcb4434d
Bump ktlint-gradle from 10.1.0 to 10.2.0
Bumps ktlint-gradle from 10.1.0 to 10.2.0.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle:ktlint-gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-09 02:06:37 +00:00
Fernando Sanz
3c46c95ac1 Code revision suggestions have been implemented. 2021-09-02 12:33:48 +02:00
Fernando Sanz
f14abb0bcd Code revision suggestions have been implemented. 2021-09-02 10:55:02 +02:00
Fernando Sanz
0895732ed4 Library's copyright has been modified and restored. 2021-09-01 13:23:49 +02:00
Fernando Sanz
91bd322b68 The ShareXMLParser has been removed from UpdateRemoteShareOperation.kt class. 2021-08-31 14:47:48 +02:00
Fernando Sanz
7baf3e43a5 The ShareXMLParser has been removed from RemoveRemoteShareOperation.kt class. 2021-08-31 12:37:36 +02:00
Fernando Sanz
6285c6c70a The ShareXMLParser has been removed from GetRemoteSharesForFileOperation.kt class. 2021-08-31 11:59:41 +02:00
Fernando Sanz
b40a394ab1 The ShareXMLParser has been removed from CreateRemoteShareOperation.kt class. 2021-08-31 10:43:29 +02:00
Fernando Sanz
037a2b30df Fixed some bugs on how to create shares. 2021-08-31 08:52:55 +02:00
Abel García de Prada
45fb12df0e Fix shares parsing 2021-08-27 15:38:49 +02:00
Fernando Sanz
27b18c33a9 The way to retrieve list of shares has been modified from java to kotlin and its result has been parsed to ShareResponse.kt object. 2021-08-27 14:08:58 +02:00
Christian Schabesberger
c24ffcfaa4
Merge pull request #425 from owncloud/fix/add_content_length_error
add error log when content-length not equal to transfaired bytes
2021-08-18 17:13:20 +02:00
Christian Schabesberger
fbf0fab800 add error log when content-length not equal to transfaired bytes 2021-08-17 12:41:35 +02:00
Abel García de Prada
5852921764
Merge pull request #420 from owncloud/release/1.0.12
[Release] 1.0.12
2021-07-20 12:55:28 +02:00
Abel García de Prada
7b5c070175 Update version name and version code 2021-07-15 09:55:21 +02:00
Abel García de Prada
650f348c35
Merge pull request #419 from owncloud/improvement/oauth2_pkce
Add PKCE support
2021-07-14 19:11:21 +02:00
Abel García de Prada
20070775d7 Add parameters required for PKCE 2021-07-14 12:40:08 +02:00
Abel García de Prada
593da77a04
Merge pull request #418 from owncloud/dependabot/gradle/kotlinVersion-1.5.21
Bump kotlinVersion from 1.5.20 to 1.5.21
2021-07-14 09:33:08 +02:00
dependabot[bot]
3025ddce23
Bump kotlinVersion from 1.5.20 to 1.5.21
Bumps `kotlinVersion` from 1.5.20 to 1.5.21.

Updates `kotlin-gradle-plugin` from 1.5.20 to 1.5.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.5.21/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.5.20...v1.5.21)

Updates `kotlin-stdlib-jdk8` from 1.5.20 to 1.5.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.5.21/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.5.20...v1.5.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib-jdk8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-14 07:06:34 +00:00
Abel García de Prada
ac2f837f9e
Merge pull request #414 from owncloud/new_arch/camera_uploads
[New arch] Camera Uploads
2021-07-14 09:05:36 +02:00
Abel García de Prada
22719c8f40 Return unit in the remote operation 2021-07-08 12:24:21 +02:00
Abel García de Prada
12b009e378 Update operation 2021-07-05 09:17:37 +02:00
Abel García de Prada
5c2be25d66 Override contentLength in content uri request body to fix uploads to ocis 2021-07-05 09:17:37 +02:00
Abel García de Prada
99e636e8f6 Add a new operation to upload files directly with a content uri 2021-07-05 09:17:37 +02:00
Abel García de Prada
022c486603
Merge pull request #415 from owncloud/bump_kotlin_gradle
Bump kotlin to 1.5.20
2021-07-02 13:07:53 +02:00
Abel García de Prada
b491641eff Bump kotlin to 1.5.20 2021-07-02 09:57:38 +02:00
Abel García de Prada
86bfc3383c
Merge pull request #410 from owncloud/dependabot/gradle/org.jlleitschuh.gradle-ktlint-gradle-10.1.0
Bump ktlint-gradle from 10.0.0 to 10.1.0
2021-06-04 08:20:47 +02:00
dependabot[bot]
58ac89cb30
Bump ktlint-gradle from 10.0.0 to 10.1.0
Bumps ktlint-gradle from 10.0.0 to 10.1.0.

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle:ktlint-gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-03 06:49:43 +00:00
Abel García de Prada
cfe5793d0d
Merge pull request #409 from owncloud/bump/kotlin_version
Bump kotlin version and AS
2021-05-26 09:38:43 +02:00
Abel García de Prada
c02a4dea91 Bump kotlin version 2021-05-26 08:57:03 +02:00
Abel García de Prada
e9d23d9cd2
Merge pull request #403 from owncloud/Gradle-6.9
Gradle 6.9
2021-05-24 09:46:36 +02:00
Hannes Achleitner
93497ca225 Gradle 6.9
It comes with Apple Silicon support
2021-05-24 09:36:33 +02:00
Abel García de Prada
abdbdbe4cc
Merge pull request #405 from owncloud/release/1.0.11
[Release] 1.0.11
2021-05-24 08:25:39 +02:00
Abel García de Prada
351682cc7f Use userId instead of username to build the webdavurl 2021-05-21 13:16:04 +02:00
Abel García de Prada
634c4a0f93 Update version name and version code for release 1.0.11 2021-05-18 12:19:40 +02:00
Abel García de Prada
307bf93a40
Merge pull request #404 from owncloud/MoveToMavenCentral
MavenCentral
2021-05-17 09:11:21 +02:00
Hannes Achleitner
d0e50c4fca MavenCentral 2021-05-15 07:07:16 +02:00
Abel García de Prada
bf6f93ff1c
Merge pull request #396 from owncloud/fix/xodo_bug
fix xodo sync bug
2021-05-11 17:09:09 +02:00
Abel García de Prada
2c18ae4ebb Clean unused imports 2021-05-10 09:56:18 +02:00
Christian Schabesberger
594ed2ee1b fix xodo file sync bug 2021-04-30 15:58:42 +02:00
Abel García de Prada
ccaf5a8e0e
Merge pull request #392 from owncloud/fix/oidc_no_registration_endpoint
Make some fields not mandatory in discovery response
2021-04-22 13:48:22 +02:00
Abel García de Prada
8c4a2708c2 Make some fields not mandatory in discovery response 2021-04-22 12:50:27 +02:00
Abel García de Prada
3f8ddd0ba9
Merge pull request #391 from owncloud/ktLintLibraryCheck
ktLint check in library
2021-04-22 12:47:51 +02:00
Abel García de Prada
13344ae622 Fix lint errors 2021-04-21 18:59:17 +02:00
Abel García de Prada
845b61ea4d Enable import ordering rule 2021-04-21 18:44:48 +02:00
Hannes Achleitner
257f616b0f ktLint check in library 2021-04-14 10:33:49 +02:00
Abel García de Prada
7dc81fb74c
Merge pull request #390 from owncloud/ktLint
Fix some ktlint findings
2021-04-14 09:45:16 +02:00
Abel García de Prada
1287035311 Fix some ktlint findings 2021-04-12 08:18:32 +02:00
Abel García de Prada
5ca9d5e330
Merge pull request #388 from owncloud/bump_kotlin_version
Bump kotlin version
2021-04-06 11:32:26 +02:00
Abel García de Prada
7924561a62 Bump kotlin version 2021-04-06 09:55:19 +02:00
Abel García de Prada
aa65410535
Merge pull request #387 from owncloud/dependabot/gradle/moshiVersion-1.12.0
Bump moshiVersion from 1.11.0 to 1.12.0
2021-04-06 09:46:04 +02:00
dependabot[bot]
88bb79c5ea
Bump moshiVersion from 1.11.0 to 1.12.0
Bumps `moshiVersion` from 1.11.0 to 1.12.0.

Updates `moshi-kotlin` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/square/moshi/releases)
- [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/moshi/compare/moshi-parent-1.11.0...parent-1.12.0)

Updates `moshi-kotlin-codegen` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/square/moshi/releases)
- [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/moshi/compare/moshi-parent-1.11.0...parent-1.12.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 09:33:44 +00:00
Abel García de Prada
87aa7137e7
Merge pull request #385 from owncloud/ktLintFindings
Fix ktlint findings
2021-04-05 08:21:24 +02:00
Hannes Achleitner
4df880357c Fix ktlint findings 2021-03-24 11:37:48 +01:00
JuancaG05
d1765eefb3
Merge pull request #384 from owncloud/bump_gradle_version
Bump gradle version to 1.4.3
2021-03-23 11:54:47 +01:00
Abel García de Prada
bf0ff3ce11 Bump gradle version to 1.4.3 2021-03-23 10:06:58 +01:00
Abel García de Prada
286fd65aff
Merge pull request #377 from owncloud/dependabot/gradle/org.robolectric-robolectric-4.5.1
Bump robolectric from 4.3.1 to 4.5.1
2021-03-22 15:07:30 +01:00
dependabot[bot]
a5574e1e45
Bump robolectric from 4.3.1 to 4.5.1
Bumps [robolectric](https://github.com/robolectric/robolectric) from 4.3.1 to 4.5.1.
- [Release notes](https://github.com/robolectric/robolectric/releases)
- [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.3.1...robolectric-4.5.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 11:27:08 +00:00
Abel García de Prada
0daa19cf90
Merge pull request #380 from owncloud/release/1.0.10
[Release] 1.0.10
2021-03-22 11:23:32 +01:00
Abel García de Prada
663e067d08 Update client with latest url 2021-03-16 12:40:51 +01:00
Abel García de Prada
a60c4909f4 Prepare 1.0.10 release 2021-03-16 11:11:39 +01:00
Abel García de Prada
b96a3e9be3
Merge pull request #376 from owncloud/release/1.0.10_beta.1
[RELEASE] 1.0.10-beta.1
2021-03-09 15:11:36 +01:00
148 changed files with 5205 additions and 4608 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*]
max_line_length = 150
[*.{kt, kts}]
disabled_rules=no-consecutive-blank-lines,no-wildcard-imports,max-line-length,no-blank-line-before-rbrace,final-newline,indent,no-multi-spaces,comment-spacing,parameter-list-wrapping

View File

@ -6,5 +6,5 @@ jobs:
name: "Validation" name: "Validation"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1 - uses: gradle/wrapper-validation-action@v1

View File

@ -49,6 +49,6 @@ ownCloud Android Library uses OkHttp version 4.6.0, licensed under Apache Licens
### Compatibility ### Compatibility
ownCloud Android Library is valid for Android systems from version Android 5 (android:minSdkVersion="21" android:targetSdkVersion="29"). ownCloud Android Library is valid for Android systems from version Android 6 (android:minSdkVersion="23" android:targetSdkVersion="33").
ownCloud Android library supports ownCloud server from version 4.5. ownCloud Android library supports ownCloud server from version 4.5.

View File

@ -1,23 +1,34 @@
buildscript { buildscript {
ext { ext {
kotlinVersion = '1.4.31' orgJetbrainsKotlin = '1.8.10'
moshiVersion = "1.11.0" comSquareupMoshi = '1.14.0'
} }
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.2' classpath "org.jlleitschuh.gradle:ktlint-gradle:11.1.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$orgJetbrainsKotlin"
} }
} }
plugins {
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
}
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
} }
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: "com.google.devtools.ksp"
}

15
check_code_script.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
check_license_in_file() {
if ! head -n 20 $FILE | grep -q "Permission is hereby granted, free of charge, to any person obtaining a copy"
then
echo "$FILE does not contain a current copyright header"
fi
}
for FILE in $(find owncloudComLibrary/src -name "*.java" -o -name "*.kt")
do
check_license_in_file
done
./gradlew ktlintFormat

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

269
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@ -106,80 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

View File

@ -1,47 +1,43 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'com.google.devtools.ksp'
apply plugin: 'kotlin-parcelize'
dependencies { dependencies {
api 'com.squareup.okhttp3:okhttp:4.6.0' api 'com.squareup.okhttp3:okhttp:4.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$orgJetbrainsKotlin"
api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5' api 'com.gitlab.ownclouders:dav4android:oc_support_2.1.5'
api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2' api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2'
// Moshi // Moshi
implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") { implementation("com.squareup.moshi:moshi-kotlin:$comSquareupMoshi") {
exclude module: "kotlin-reflect" exclude module: "kotlin-reflect"
} }
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" implementation 'org.apache.commons:commons-lang3:3.12.0'
ksp "com.squareup.moshi:moshi-kotlin-codegen:$comSquareupMoshi"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'org.robolectric:robolectric:4.10'
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
} }
android { android {
compileSdkVersion 29 compileSdkVersion 33
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 33
versionCode = 10000901
versionName = "1.0.10-beta.1"
} }
lintOptions { lint {
abortOnError false abortOnError false
ignoreWarnings true ignoreWarnings true
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions { testOptions {
unitTests { unitTests {
includeAndroidResources = true includeAndroidResources = true
} }
} }
namespace 'com.owncloud.android.lib'
} }

View File

@ -0,0 +1,30 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.common.http
import com.facebook.stetho.okhttp3.StethoInterceptor
object DebugInterceptorFactory {
fun getInterceptor() = StethoInterceptor()
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- ownCloud Android Library is available under MIT license <!-- ownCloud Android Library is available under MIT license
Copyright (C) 2016 ownCloud GmbH. Copyright (C) 2023 ownCloud GmbH.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -23,16 +23,8 @@
--> -->
<manifest package="com.owncloud.android.lib" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- USE_CREDENTIALS, MANAGE_ACCOUNTS and AUTHENTICATE_ACCOUNTS are needed for API < 23.
In API >= 23 the do not exist anymore -->
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest> </manifest>

View File

@ -0,0 +1,221 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common
import android.accounts.AccountManager
import android.accounts.AccountsException
import android.content.Context
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation
import com.owncloud.android.lib.resources.status.RemoteServerInfo
import org.apache.commons.lang3.exception.ExceptionUtils
import timber.log.Timber
import java.io.IOException
/**
* ConnectionValidator
*
* @author Christian Schabesberger
*/
class ConnectionValidator(
val context: Context,
private val clearCookiesOnValidation: Boolean
) {
fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager, context: Context): Boolean {
try {
var validationRetryCount = 0
val client = OwnCloudClient(baseClient.baseUri, null, false, singleSessionManager, context)
if (clearCookiesOnValidation) {
client.clearCookies()
} else {
client.cookiesForBaseUri = baseClient.cookiesForBaseUri
}
client.account = baseClient.account
client.credentials = baseClient.credentials
while (validationRetryCount < VALIDATION_RETRY_COUNT) {
Timber.d("validationRetryCount %d", validationRetryCount)
var successCounter = 0
var failCounter = 0
client.setFollowRedirects(true)
if (isOwnCloudStatusOk(client)) {
successCounter++
} else {
failCounter++
}
// Skip the part where we try to check if we can access the parts where we have to be logged in... if we are not logged in
if (baseClient.credentials !is OwnCloudAnonymousCredentials) {
client.setFollowRedirects(false)
val contentReply = canAccessRootFolder(client)
if (contentReply.httpCode == HttpConstants.HTTP_OK) {
if (contentReply.data == true) { //if data is true it means that the content reply was ok
successCounter++
} else {
failCounter++
}
} else {
failCounter++
if (contentReply.httpCode == HttpConstants.HTTP_UNAUTHORIZED) {
checkUnauthorizedAccess(client, singleSessionManager, contentReply.httpCode)
}
}
}
if (successCounter >= failCounter) {
baseClient.credentials = client.credentials
baseClient.cookiesForBaseUri = client.cookiesForBaseUri
return true
}
validationRetryCount++
}
Timber.d("Could not authenticate or get valid data from owncloud")
} catch (e: Exception) {
Timber.d(ExceptionUtils.getStackTrace(e))
}
return false
}
private fun isOwnCloudStatusOk(client: OwnCloudClient): Boolean {
val reply = getOwnCloudStatus(client)
// dont check status code. It currently relais on the broken redirect code of the owncloud client
// TODO: Use okhttp redirect and add this check again
// return reply.httpCode == HttpConstants.HTTP_OK &&
return !reply.isException &&
reply.data != null
}
private fun getOwnCloudStatus(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> {
val remoteStatusOperation = GetRemoteStatusOperation()
return remoteStatusOperation.execute(client)
}
private fun canAccessRootFolder(client: OwnCloudClient): RemoteOperationResult<Boolean> {
val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation("/", true)
return checkPathExistenceRemoteOperation.execute(client)
}
/**
* Determines if credentials should be invalidated according the to the HTTPS status
* of a network request just performed.
*
* @param httpStatusCode Result of the last request ran with the 'credentials' belows.
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
* cannot be invalidated with the given arguments.
*/
private fun shouldInvalidateAccountCredentials(credentials: OwnCloudCredentials, account: OwnCloudAccount, httpStatusCode: Int): Boolean {
var shouldInvalidateAccountCredentials = httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED
shouldInvalidateAccountCredentials = shouldInvalidateAccountCredentials and // real credentials
(credentials !is OwnCloudAnonymousCredentials)
// test if have all the needed to effectively invalidate ...
shouldInvalidateAccountCredentials =
shouldInvalidateAccountCredentials and (account.savedAccount != null)
Timber.d(
"""Received error: $httpStatusCode,
account: ${account.name}
credentials are real: ${credentials !is OwnCloudAnonymousCredentials},
so we need to invalidate credentials for account ${account.name} : $shouldInvalidateAccountCredentials"""
)
return shouldInvalidateAccountCredentials
}
/**
* Invalidates credentials stored for the given account in the system [AccountManager] and in
* current [SingleSessionManager.getDefaultSingleton] instance.
*
*
* [.shouldInvalidateAccountCredentials] should be called first.
*
*/
private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) {
Timber.i("Invalidating account credentials for account $account")
val am = AccountManager.get(context)
am.invalidateAuthToken(
account.savedAccount.type,
credentials.authToken
)
am.clearPassword(account.savedAccount) // being strict, only needed for Basic Auth credentials
}
/**
* Checks the status code of an execution and decides if should be repeated with fresh credentials.
*
*
* Invalidates current credentials if the request failed as anauthorized.
*
*
* Refresh current credentials if possible, and marks a retry.
*
* @return
*/
private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean {
var credentialsWereRefreshed = false
val account = client.account
val credentials = account.credentials
if (shouldInvalidateAccountCredentials(credentials, account, status)) {
invalidateAccountCredentials(account, credentials)
if (credentials.authTokenCanBeRefreshed()) {
try {
// This command does the actual refresh
Timber.i("Trying to refresh auth token for account $account")
account.loadCredentials(context)
// if mAccount.getCredentials().length() == 0 --> refresh failed
client.credentials = account.credentials
credentialsWereRefreshed = true
} catch (e: AccountsException) {
Timber.e(
e, "Error while trying to refresh auth token for %s\ntrace: %s",
account.savedAccount.name,
ExceptionUtils.getStackTrace(e)
)
} catch (e: IOException) {
Timber.e(
e, "Error while trying to refresh auth token for %s\ntrace: %s",
account.savedAccount.name,
ExceptionUtils.getStackTrace(e)
)
}
if (!credentialsWereRefreshed) {
// if credentials are not refreshed, client must be removed
// from the OwnCloudClientManager to prevent it is reused once and again
Timber.w("Credentials were not refreshed, client will be removed from the Session Manager to prevent using it over and over")
singleSessionManager.removeClientFor(account)
}
}
// else: onExecute will finish with status 401
}
return credentialsWereRefreshed
}
companion object {
private const val VALIDATION_RETRY_COUNT = 3
}
}

View File

@ -25,11 +25,9 @@
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common;
import android.accounts.AccountManager; import android.content.Context;
import android.accounts.AccountsException;
import android.net.Uri; import android.net.Uri;
import at.bitfire.dav4jvm.exception.HttpException;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory;
@ -37,9 +35,7 @@ import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory
import com.owncloud.android.lib.common.http.HttpClient; import com.owncloud.android.lib.common.http.HttpClient;
import com.owncloud.android.lib.common.http.HttpConstants; import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; import com.owncloud.android.lib.common.http.methods.HttpBaseMethod;
import com.owncloud.android.lib.common.network.RedirectionPath;
import com.owncloud.android.lib.common.utils.RandomUtils; import com.owncloud.android.lib.common.utils.RandomUtils;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import okhttp3.Cookie; import okhttp3.Cookie;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import timber.log.Timber; import timber.log.Timber;
@ -47,42 +43,56 @@ import timber.log.Timber;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Locale;
import static com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER; import static com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER;
import static com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID; import static com.owncloud.android.lib.common.http.HttpConstants.HTTP_MOVED_PERMANENTLY;
public class OwnCloudClient extends HttpClient { public class OwnCloudClient extends HttpClient {
public static final String WEBDAV_FILES_PATH_4_0 = "/remote.php/dav/files/"; public static final String WEBDAV_FILES_PATH_4_0 = "/remote.php/dav/files/";
public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/dav";
public static final String STATUS_PATH = "/status.php"; public static final String STATUS_PATH = "/status.php";
private static final String WEBDAV_UPLOADS_PATH_4_0 = "/remote.php/dav/uploads/"; private static final String WEBDAV_UPLOADS_PATH_4_0 = "/remote.php/dav/uploads/";
private static final int MAX_REDIRECTIONS_COUNT = 3; private static final int MAX_RETRY_COUNT = 2;
private static final int MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS = 1;
private static byte[] sExhaustBuffer = new byte[1024];
private static int sIntanceCounter = 0; private static int sIntanceCounter = 0;
private OwnCloudCredentials mCredentials = null; private OwnCloudCredentials mCredentials = null;
private int mInstanceNumber; private int mInstanceNumber;
private Uri mBaseUri; private Uri mBaseUri;
private OwnCloudVersion mVersion = null;
private OwnCloudAccount mAccount; private OwnCloudAccount mAccount;
private final ConnectionValidator mConnectionValidator;
private Object mRequestMutex = new Object();
// If set to true a mutex will be used to prevent parallel execution of the execute() method
// if false the execute() method can be called even though the mutex is already aquired.
// This is used for the ConnectionValidator, which has to be able to execute OperationsWhile all "normal" operations net
// to be set on hold.
private final Boolean mSynchronizeRequests;
private SingleSessionManager mSingleSessionManager = null; private SingleSessionManager mSingleSessionManager = null;
private boolean mFollowRedirects; private boolean mFollowRedirects = false;
public OwnCloudClient(Uri baseUri,
ConnectionValidator connectionValidator,
boolean synchronizeRequests,
SingleSessionManager singleSessionManager,
Context context) {
super(context);
public OwnCloudClient(Uri baseUri) {
if (baseUri == null) { if (baseUri == null) {
throw new IllegalArgumentException("Parameter 'baseUri' cannot be NULL"); throw new IllegalArgumentException("Parameter 'baseUri' cannot be NULL");
} }
mBaseUri = baseUri; mBaseUri = baseUri;
mSynchronizeRequests = synchronizeRequests;
mSingleSessionManager = singleSessionManager;
mInstanceNumber = sIntanceCounter++; mInstanceNumber = sIntanceCounter++;
Timber.d("#" + mInstanceNumber + "Creating OwnCloudClient"); Timber.d("#" + mInstanceNumber + "Creating OwnCloudClient");
clearCredentials(); clearCredentials();
clearCookies(); clearCookies();
mConnectionValidator = connectionValidator;
} }
public void clearCredentials() { public void clearCredentials() {
@ -92,117 +102,63 @@ public class OwnCloudClient extends HttpClient {
} }
public int executeHttpMethod(HttpBaseMethod method) throws Exception { public int executeHttpMethod(HttpBaseMethod method) throws Exception {
boolean repeatWithFreshCredentials; if (mSynchronizeRequests) {
synchronized (mRequestMutex) {
return saveExecuteHttpMethod(method);
}
} else {
return saveExecuteHttpMethod(method);
}
}
private int saveExecuteHttpMethod(HttpBaseMethod method) throws Exception {
int repeatCounter = 0; int repeatCounter = 0;
int status; int status;
if (mFollowRedirects) {
method.setFollowRedirects(true);
}
boolean retry;
do { do {
repeatCounter++;
retry = false;
String requestId = RandomUtils.generateRandomUUID(); String requestId = RandomUtils.generateRandomUUID();
// Header to allow tracing requests in apache and ownCloud logs // Header to allow tracing requests in apache and ownCloud logs
Timber.d("Executing in request with id %s", requestId); Timber.d("Executing in request with id %s", requestId);
method.setRequestHeader(HttpConstants.OC_X_REQUEST_ID, requestId); method.setRequestHeader(HttpConstants.OC_X_REQUEST_ID, requestId);
method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent()); method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent());
method.setRequestHeader(HttpConstants.ACCEPT_LANGUAGE_HEADER, Locale.getDefault().getLanguage());
method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY); method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY);
if (mCredentials.getHeaderAuth() != null && method.getRequestHeader(AUTHORIZATION_HEADER) == null) { if (mCredentials.getHeaderAuth() != null && !mCredentials.getHeaderAuth().isEmpty()) {
method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth()); method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth());
} }
status = method.execute();
if (mFollowRedirects) { status = method.execute(this);
status = followRedirection(method).getLastStatus();
if (shouldConnectionValidatorBeCalled(method, status)) {
retry = mConnectionValidator.validate(this, mSingleSessionManager, getContext()); // retry on success fail on no success
} else if (method.getFollowPermanentRedirects() && status == HTTP_MOVED_PERMANENTLY) {
retry = true;
method.setFollowRedirects(true);
} }
repeatWithFreshCredentials = checkUnauthorizedAccess(status, repeatCounter); } while (retry && repeatCounter < MAX_RETRY_COUNT);
if (repeatWithFreshCredentials) {
repeatCounter++;
}
} while (repeatWithFreshCredentials);
return status; return status;
} }
private int executeRedirectedHttpMethod(HttpBaseMethod method) throws Exception { private boolean shouldConnectionValidatorBeCalled(HttpBaseMethod method, int status) {
boolean repeatWithFreshCredentials;
int repeatCounter = 0;
int status;
do { return mConnectionValidator != null && (
String requestId = RandomUtils.generateRandomUUID(); (!(mCredentials instanceof OwnCloudAnonymousCredentials) &&
status == HttpConstants.HTTP_UNAUTHORIZED
// Header to allow tracing requests in apache and ownCloud logs ) || (!mFollowRedirects &&
Timber.d("Executing in request with id %s", requestId); !method.getFollowRedirects() &&
method.setRequestHeader(OC_X_REQUEST_ID, requestId); status == HttpConstants.HTTP_MOVED_TEMPORARILY
method.setRequestHeader(HttpConstants.USER_AGENT_HEADER, SingleSessionManager.getUserAgent()); )
method.setRequestHeader(HttpConstants.ACCEPT_ENCODING_HEADER, HttpConstants.ACCEPT_ENCODING_IDENTITY); );
if (mCredentials.getHeaderAuth() != null) {
method.setRequestHeader(AUTHORIZATION_HEADER, mCredentials.getHeaderAuth());
}
status = method.execute();
repeatWithFreshCredentials = checkUnauthorizedAccess(status, repeatCounter);
if (repeatWithFreshCredentials) {
repeatCounter++;
}
} while (repeatWithFreshCredentials);
return status;
}
public RedirectionPath followRedirection(HttpBaseMethod method) throws Exception {
int redirectionsCount = 0;
int status = method.getStatusCode();
RedirectionPath redirectionPath = new RedirectionPath(status, MAX_REDIRECTIONS_COUNT);
while (redirectionsCount < MAX_REDIRECTIONS_COUNT &&
(status == HttpConstants.HTTP_MOVED_PERMANENTLY ||
status == HttpConstants.HTTP_MOVED_TEMPORARILY ||
status == HttpConstants.HTTP_TEMPORARY_REDIRECT)
) {
final String location = method.getResponseHeader(HttpConstants.LOCATION_HEADER) != null
? method.getResponseHeader(HttpConstants.LOCATION_HEADER)
: method.getResponseHeader(HttpConstants.LOCATION_HEADER_LOWER);
if (location != null) {
Timber.d("#" + mInstanceNumber + "Location to redirect: " + location);
redirectionPath.addLocation(location);
// Release the connection to avoid reach the max number of connections per host
// due to it will be set a different url
exhaustResponse(method.getResponseBodyAsStream());
method.setUrl(HttpUrl.parse(location));
final String destination = method.getRequestHeader("Destination") != null
? method.getRequestHeader("Destination")
: method.getRequestHeader("destination");
if (destination != null) {
final int suffixIndex = location.lastIndexOf(getUserFilesWebDavUri().toString());
final String redirectionBase = location.substring(0, suffixIndex);
final String destinationPath = destination.substring(mBaseUri.toString().length());
method.setRequestHeader("destination", redirectionBase + destinationPath);
}
try {
status = executeRedirectedHttpMethod(method);
} catch (HttpException e) {
if (e.getMessage().contains(Integer.toString(HttpConstants.HTTP_MOVED_TEMPORARILY))) {
status = HttpConstants.HTTP_MOVED_TEMPORARILY;
} else {
throw e;
}
}
redirectionPath.addStatus(status);
redirectionsCount++;
} else {
Timber.d(" #" + mInstanceNumber + "No location to redirect!");
status = HttpConstants.HTTP_NOT_FOUND;
}
}
return redirectionPath;
} }
/** /**
@ -229,7 +185,7 @@ public class OwnCloudClient extends HttpClient {
return (mCredentials instanceof OwnCloudAnonymousCredentials || mAccount == null) return (mCredentials instanceof OwnCloudAnonymousCredentials || mAccount == null)
? Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0) ? Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0)
: Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0 + AccountUtils.getUserId( : Uri.parse(mBaseUri + WEBDAV_FILES_PATH_4_0 + AccountUtils.getUserId(
mAccount.getSavedAccount(), getContext() mAccount.getSavedAccount(), getContext()
) )
); );
} }
@ -238,7 +194,7 @@ public class OwnCloudClient extends HttpClient {
return mCredentials instanceof OwnCloudAnonymousCredentials return mCredentials instanceof OwnCloudAnonymousCredentials
? Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0) ? Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0)
: Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0 + AccountUtils.getUserId( : Uri.parse(mBaseUri + WEBDAV_UPLOADS_PATH_4_0 + AccountUtils.getUserId(
mAccount.getSavedAccount(), getContext() mAccount.getSavedAccount(), getContext()
) )
); );
} }
@ -273,32 +229,16 @@ public class OwnCloudClient extends HttpClient {
} }
} }
public String getCookiesString() { public void setCookiesForBaseUri(List<Cookie> cookies) {
StringBuilder cookiesString = new StringBuilder();
List<Cookie> cookieList = getCookiesFromUrl(HttpUrl.parse(mBaseUri.toString()));
if (cookieList != null) {
for (Cookie cookie : cookieList) {
cookiesString.append(cookie.toString()).append(";");
}
}
return cookiesString.toString();
}
public void setCookiesForCurrentAccount(List<Cookie> cookies) {
getOkHttpClient().cookieJar().saveFromResponse( getOkHttpClient().cookieJar().saveFromResponse(
HttpUrl.parse(getAccount().getBaseUri().toString()), HttpUrl.parse(mBaseUri.toString()),
cookies cookies
); );
} }
public OwnCloudVersion getOwnCloudVersion() { public List<Cookie> getCookiesForBaseUri() {
return mVersion; return getOkHttpClient().cookieJar().loadForRequest(
} HttpUrl.parse(mBaseUri.toString()));
public void setOwnCloudVersion(OwnCloudVersion version) {
mVersion = version;
} }
public OwnCloudAccount getAccount() { public OwnCloudAccount getAccount() {
@ -309,94 +249,6 @@ public class OwnCloudClient extends HttpClient {
this.mAccount = account; this.mAccount = account;
} }
/**
* Checks the status code of an execution and decides if should be repeated with fresh credentials.
* <p>
* Invalidates current credentials if the request failed as anauthorized.
* <p>
* Refresh current credentials if possible, and marks a retry.
*
* @param status
* @param repeatCounter
* @return
*/
private boolean checkUnauthorizedAccess(int status, int repeatCounter) {
boolean credentialsWereRefreshed = false;
if (shouldInvalidateAccountCredentials(status)) {
boolean invalidated = invalidateAccountCredentials();
if (invalidated) {
if (getCredentials().authTokenCanBeRefreshed() &&
repeatCounter < MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS) {
try {
mAccount.loadCredentials(getContext());
// if mAccount.getCredentials().length() == 0 --> refresh failed
setCredentials(mAccount.getCredentials());
credentialsWereRefreshed = true;
} catch (AccountsException | IOException e) {
Timber.e(e, "Error while trying to refresh auth token for %s",
mAccount.getSavedAccount().name
);
}
}
if (!credentialsWereRefreshed && mSingleSessionManager != null) {
// if credentials are not refreshed, client must be removed
// from the OwnCloudClientManager to prevent it is reused once and again
mSingleSessionManager.removeClientFor(mAccount);
}
}
// else: onExecute will finish with status 401
}
return credentialsWereRefreshed;
}
/**
* Determines if credentials should be invalidated according the to the HTTPS status
* of a network request just performed.
*
* @param httpStatusCode Result of the last request ran with the 'credentials' belows.
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
* cannot be invalidated with the given arguments.
*/
private boolean shouldInvalidateAccountCredentials(int httpStatusCode) {
boolean shouldInvalidateAccountCredentials =
(httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED);
shouldInvalidateAccountCredentials &= (mCredentials != null && // real credentials
!(mCredentials instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials));
// test if have all the needed to effectively invalidate ...
shouldInvalidateAccountCredentials &= (mAccount != null && mAccount.getSavedAccount() != null && getContext() != null);
return shouldInvalidateAccountCredentials;
}
/**
* Invalidates credentials stored for the given account in the system {@link AccountManager} and in
* current {@link SingleSessionManager#getDefaultSingleton()} instance.
* <p>
* {@link #shouldInvalidateAccountCredentials(int)} should be called first.
*
* @return 'True' if invalidation was successful, 'false' otherwise.
*/
private boolean invalidateAccountCredentials() {
AccountManager am = AccountManager.get(getContext());
am.invalidateAuthToken(
mAccount.getSavedAccount().type,
mCredentials.getAuthToken()
);
am.clearPassword(mAccount.getSavedAccount()); // being strict, only needed for Basic Auth credentials
return true;
}
public boolean followRedirects() {
return mFollowRedirects;
}
public void setFollowRedirects(boolean followRedirects) { public void setFollowRedirects(boolean followRedirects) {
this.mFollowRedirects = followRedirects; this.mFollowRedirects = followRedirects;
} }

View File

@ -1,58 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common;
import android.content.Context;
import android.net.Uri;
import com.owncloud.android.lib.common.http.HttpClient;
import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation;
public class OwnCloudClientFactory {
/**
* Creates a OwnCloudClient to access a URL and sets the desired parameters for ownCloud
* client connections.
*
* @param uri URL to the ownCloud server; BASE ENTRY POINT, not WebDavPATH
* @param context Android context where the OwnCloudClient is being created.
* @return A OwnCloudClient object ready to be used
*/
public static OwnCloudClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects) {
OwnCloudClient client = new OwnCloudClient(uri);
client.setFollowRedirects(followRedirects);
HttpClient.setContext(context);
retrieveCookiesFromMiddleware(client);
return client;
}
private static void retrieveCookiesFromMiddleware(OwnCloudClient client) {
final GetRemoteStatusOperation statusOperation = new GetRemoteStatusOperation();
statusOperation.run(client);
}
}

View File

@ -31,7 +31,6 @@ import android.net.Uri;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.http.HttpClient;
import timber.log.Timber; import timber.log.Timber;
import java.io.IOException; import java.io.IOException;
@ -49,6 +48,7 @@ public class SingleSessionManager {
private static SingleSessionManager sDefaultSingleton; private static SingleSessionManager sDefaultSingleton;
private static String sUserAgent; private static String sUserAgent;
private static ConnectionValidator sConnectionValidator;
private ConcurrentMap<String, OwnCloudClient> mClientsWithKnownUsername = new ConcurrentHashMap<>(); private ConcurrentMap<String, OwnCloudClient> mClientsWithKnownUsername = new ConcurrentHashMap<>();
private ConcurrentMap<String, OwnCloudClient> mClientsWithUnknownUsername = new ConcurrentHashMap<>(); private ConcurrentMap<String, OwnCloudClient> mClientsWithUnknownUsername = new ConcurrentHashMap<>();
@ -60,6 +60,14 @@ public class SingleSessionManager {
return sDefaultSingleton; return sDefaultSingleton;
} }
public static void setConnectionValidator(ConnectionValidator connectionValidator) {
sConnectionValidator = connectionValidator;
}
public static ConnectionValidator getConnectionValidator() {
return sConnectionValidator;
}
public static String getUserAgent() { public static String getUserAgent() {
return sUserAgent; return sUserAgent;
} }
@ -68,7 +76,23 @@ public class SingleSessionManager {
sUserAgent = userAgent; sUserAgent = userAgent;
} }
public OwnCloudClient getClientFor(OwnCloudAccount account, Context context) throws OperationCanceledException, private static OwnCloudClient createOwnCloudClient(Uri uri,
Context context,
ConnectionValidator connectionValidator,
SingleSessionManager singleSessionManager) {
OwnCloudClient client = new OwnCloudClient(uri, connectionValidator, true, singleSessionManager, context);
return client;
}
public OwnCloudClient getClientFor(OwnCloudAccount account,
Context context) throws OperationCanceledException,
AuthenticatorException, IOException {
return getClientFor(account, context, getConnectionValidator());
}
public OwnCloudClient getClientFor(OwnCloudAccount account,
Context context,
ConnectionValidator connectionValidator) throws OperationCanceledException,
AuthenticatorException, IOException { AuthenticatorException, IOException {
Timber.d("getClientFor starting "); Timber.d("getClientFor starting ");
@ -99,15 +123,34 @@ public class SingleSessionManager {
} }
} else { } else {
Timber.v("reusing client for account %s", accountName); Timber.v("reusing client for account %s", accountName);
if (client.getAccount() != null &&
client.getAccount().getCredentials() != null &&
(client.getAccount().getCredentials().getAuthToken() == null || client.getAccount().getCredentials().getAuthToken().isEmpty())
) {
Timber.i("Client " + client.getAccount().getName() + " needs to refresh credentials");
//the next two lines are a hack because okHttpclient is used as a singleton instead of being an
//injected instance that can be deleted when required
client.clearCookies();
client.clearCredentials();
client.setAccount(account);
account.loadCredentials(context);
client.setCredentials(account.getCredentials());
Timber.i("Client " + account.getName() + " with credentials size" + client.getAccount().getCredentials().getAuthToken().length());
}
reusingKnown = true; reusingKnown = true;
} }
if (client == null) { if (client == null) {
// no client to reuse - create a new one // no client to reuse - create a new one
client = OwnCloudClientFactory.createOwnCloudClient( client = createOwnCloudClient(
account.getBaseUri(), account.getBaseUri(),
context.getApplicationContext(), context,
true); // TODO remove dependency on OwnCloudClientFactory connectionValidator,
this); // TODO remove dependency on OwnCloudClientFactory
//the next two lines are a hack because okHttpclient is used as a singleton instead of being an //the next two lines are a hack because okHttpclient is used as a singleton instead of being an
//injected instance that can be deleted when required //injected instance that can be deleted when required
@ -115,7 +158,6 @@ public class SingleSessionManager {
client.clearCredentials(); client.clearCredentials();
client.setAccount(account); client.setAccount(account);
HttpClient.setContext(context);
account.loadCredentials(context); account.loadCredentials(context);
client.setCredentials(account.getCredentials()); client.setCredentials(account.getCredentials());

View File

@ -52,17 +52,9 @@ public class AccountUtils {
*/ */
public static String getWebDavUrlForAccount(Context context, Account account) public static String getWebDavUrlForAccount(Context context, Account account)
throws AccountNotFoundException { throws AccountNotFoundException {
String webDavUrlForAccount = "";
try { return getBaseUrlForAccount(context, account) + OwnCloudClient.WEBDAV_FILES_PATH_4_0
OwnCloudCredentials ownCloudCredentials = getCredentialsForAccount(context, account); + AccountUtils.getUserId(account, context);
webDavUrlForAccount = getBaseUrlForAccount(context, account) + OwnCloudClient.WEBDAV_FILES_PATH_4_0
+ ownCloudCredentials.getUsername();
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
Timber.e(e);
}
return webDavUrlForAccount;
} }
/** /**
@ -102,26 +94,6 @@ public class AccountUtils {
return username; return username;
} }
/**
* Get the stored server version corresponding to an OC account.
*
* @param account An OC account
* @param context Application context
* @return Version of the OC server, according to last check
*/
public static OwnCloudVersion getServerVersionForAccount(Account account, Context context) {
AccountManager ama = AccountManager.get(context);
OwnCloudVersion version = null;
try {
String versionString = ama.getUserData(account, Constants.KEY_OC_VERSION);
version = new OwnCloudVersion(versionString);
} catch (Exception e) {
Timber.e(e, "Couldn't get a the server version for an account");
}
return version;
}
/** /**
* @return * @return
* @throws IOException * @throws IOException
@ -140,6 +112,7 @@ public class AccountUtils {
String username = AccountUtils.getUsernameForAccount(account); String username = AccountUtils.getUsernameForAccount(account);
if (isOauth2) { if (isOauth2) {
Timber.i("Trying to retrieve credentials for oAuth account" + account.name);
String accessToken = am.blockingGetAuthToken( String accessToken = am.blockingGetAuthToken(
account, account,
AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), AccountTypeUtils.getAuthTokenTypeAccessToken(account.type),
@ -217,11 +190,6 @@ public class AccountUtils {
} }
public static class Constants { public static class Constants {
/**
* Version should be 3 numbers separated by dot so it can be parsed by
* {@link OwnCloudVersion}
*/
public static final String KEY_OC_VERSION = "oc_version";
/** /**
* Base url should point to owncloud installation without trailing / ie: * Base url should point to owncloud installation without trailing / ie:
* http://server/path or https://owncloud.server * http://server/path or https://owncloud.server

View File

@ -28,7 +28,7 @@ import okhttp3.CookieJar
import okhttp3.HttpUrl import okhttp3.HttpUrl
class CookieJarImpl( class CookieJarImpl(
private val sCookieStore: HashMap<String, List<Cookie>> private val cookieStore: HashMap<String, List<Cookie>>
) : CookieJar { ) : CookieJar {
fun containsCookieWithName(cookies: List<Cookie>, name: String): Boolean { fun containsCookieWithName(cookies: List<Cookie>, name: String): Boolean {
@ -52,12 +52,11 @@ class CookieJarImpl(
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
// Avoid duplicated cookies but update // Avoid duplicated cookies but update
val currentCookies: List<Cookie> = sCookieStore[url.host] ?: ArrayList() val currentCookies: List<Cookie> = cookieStore[url.host] ?: ArrayList()
val updatedCookies: List<Cookie> = getUpdatedCookies(currentCookies, cookies) val updatedCookies: List<Cookie> = getUpdatedCookies(currentCookies, cookies)
sCookieStore[url.host] = updatedCookies cookieStore[url.host] = updatedCookies
} }
override fun loadForRequest(url: HttpUrl) = override fun loadForRequest(url: HttpUrl) =
sCookieStore[url.host] ?: ArrayList() cookieStore[url.host] ?: ArrayList()
}
}

View File

@ -21,26 +21,11 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.sampleclient;
import android.content.Context; package com.owncloud.android.lib.common.http
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.owncloud.android.lib.resources.files.RemoteFile; import okhttp3.Interceptor
public class FilesArrayAdapter extends ArrayAdapter<RemoteFile> {
public FilesArrayAdapter(Context context, int resource) {
super(context, resource);
}
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setText(getItem(position).getRemotePath());
return textView;
}
}
class DummyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) = chain.proceed(chain.request())
}

View File

@ -40,7 +40,6 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -54,32 +53,46 @@ import java.util.concurrent.TimeUnit;
*/ */
public class HttpClient { public class HttpClient {
private static OkHttpClient sOkHttpClient; private Context mContext;
private static Context sContext; private HashMap<String, List<Cookie>> mCookieStore = new HashMap<>();
private static HashMap<String, List<Cookie>> sCookieStore = new HashMap<>(); private LogInterceptor mLogInterceptor = new LogInterceptor();
private static LogInterceptor sLogInterceptor;
public static OkHttpClient getOkHttpClient() { private OkHttpClient mOkHttpClient = null;
if (sOkHttpClient == null) {
try {
final X509TrustManager trustManager = new AdvancedX509TrustManager(
NetworkUtils.getKnownServersStore(sContext));
final SSLSocketFactory sslSocketFactory = getNewSslSocketFactory(trustManager);
// Automatic cookie handling, NOT PERSISTENT
final CookieJar cookieJar = new CookieJarImpl(sCookieStore);
// TODO: Not verifying the hostname against certificate. ask owncloud security human if this is ok. protected HttpClient(Context context) {
//.hostnameVerifier(new BrowserCompatHostnameVerifier()); if (context == null) {
sOkHttpClient = buildNewOkHttpClient(sslSocketFactory, trustManager, cookieJar); Timber.e("Context may not be NULL!");
throw new NullPointerException("Context may not be NULL!");
} catch (Exception e) {
Timber.e(e, "Could not setup SSL system.");
}
} }
return sOkHttpClient; mContext = context;
} }
private static SSLContext getSslContext() throws NoSuchAlgorithmException { public OkHttpClient getOkHttpClient() {
if (mOkHttpClient == null) {
try {
final X509TrustManager trustManager = new AdvancedX509TrustManager(
NetworkUtils.getKnownServersStore(mContext));
final SSLContext sslContext = buildSSLContext();
sslContext.init(null, new TrustManager[]{trustManager}, null);
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// Automatic cookie handling, NOT PERSISTENT
final CookieJar cookieJar = new CookieJarImpl(mCookieStore);
mOkHttpClient = buildNewOkHttpClient(sslSocketFactory, trustManager, cookieJar);
} catch (NoSuchAlgorithmException nsae) {
Timber.e(nsae, "Could not setup SSL system.");
throw new RuntimeException("Could not setup okHttp client.", nsae);
} catch (Exception e) {
Timber.e(e, "Could not setup okHttp client.");
throw new RuntimeException("Could not setup okHttp client.", e);
}
}
return mOkHttpClient;
}
private SSLContext buildSSLContext() throws NoSuchAlgorithmException {
try { try {
return SSLContext.getInstance(TlsVersion.TLS_1_3.javaName()); return SSLContext.getInstance(TlsVersion.TLS_1_3.javaName());
} catch (NoSuchAlgorithmException tlsv13Exception) { } catch (NoSuchAlgorithmException tlsv13Exception) {
@ -100,17 +113,11 @@ public class HttpClient {
} }
} }
private static SSLSocketFactory getNewSslSocketFactory(X509TrustManager trustManager) private OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager,
throws NoSuchAlgorithmException, KeyManagementException { CookieJar cookieJar) {
final SSLContext sslContext = getSslContext();
sslContext.init(null, new TrustManager[]{trustManager}, null);
return sslContext.getSocketFactory();
}
private static OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager,
CookieJar cookieJar) {
return new OkHttpClient.Builder() return new OkHttpClient.Builder()
.addNetworkInterceptor(getLogInterceptor()) .addNetworkInterceptor(getLogInterceptor())
.addNetworkInterceptor(DebugInterceptorFactory.INSTANCE.getInterceptor())
.protocols(Collections.singletonList(Protocol.HTTP_1_1)) .protocols(Collections.singletonList(Protocol.HTTP_1_1))
.readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS)
@ -122,26 +129,19 @@ public class HttpClient {
.build(); .build();
} }
public static LogInterceptor getLogInterceptor() {
if (sLogInterceptor == null) {
sLogInterceptor = new LogInterceptor();
}
return sLogInterceptor;
}
public static List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
return sCookieStore.get(httpUrl.host());
}
public Context getContext() { public Context getContext() {
return sContext; return mContext;
} }
public static void setContext(Context context) { public LogInterceptor getLogInterceptor() {
sContext = context; return mLogInterceptor;
}
public List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) {
return mCookieStore.get(httpUrl.host());
} }
public void clearCookies() { public void clearCookies() {
sCookieStore.clear(); mCookieStore.clear();
} }
} }

View File

@ -40,6 +40,7 @@ public class HttpConstants {
public static final String IF_MATCH_HEADER = "If-Match"; public static final String IF_MATCH_HEADER = "If-Match";
public static final String IF_NONE_MATCH_HEADER = "If-None-Match"; public static final String IF_NONE_MATCH_HEADER = "If-None-Match";
public static final String CONTENT_TYPE_HEADER = "Content-Type"; public static final String CONTENT_TYPE_HEADER = "Content-Type";
public static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
public static final String CONTENT_LENGTH_HEADER = "Content-Length"; public static final String CONTENT_LENGTH_HEADER = "Content-Length";
public static final String OC_TOTAL_LENGTH_HEADER = "OC-Total-Length"; public static final String OC_TOTAL_LENGTH_HEADER = "OC-Total-Length";
public static final String OC_X_OC_MTIME_HEADER = "X-OC-Mtime"; public static final String OC_X_OC_MTIME_HEADER = "X-OC-Mtime";
@ -56,6 +57,7 @@ public class HttpConstants {
public static final String OAUTH_HEADER_GRANT_TYPE = "grant_type"; public static final String OAUTH_HEADER_GRANT_TYPE = "grant_type";
public static final String OAUTH_HEADER_REDIRECT_URI = "redirect_uri"; public static final String OAUTH_HEADER_REDIRECT_URI = "redirect_uri";
public static final String OAUTH_HEADER_REFRESH_TOKEN = "refresh_token"; public static final String OAUTH_HEADER_REFRESH_TOKEN = "refresh_token";
public static final String OAUTH_HEADER_CODE_VERIFIER = "code_verifier";
/*********************************************************************************************************** /***********************************************************************************************************
************************************************ CONTENT TYPES ******************************************** ************************************************ CONTENT TYPES ********************************************
@ -65,6 +67,18 @@ public class HttpConstants {
public static final String CONTENT_TYPE_JSON = "application/json"; public static final String CONTENT_TYPE_JSON = "application/json";
public static final String CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded"; public static final String CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded";
/***********************************************************************************************************
************************************************ ARGUMENTS NAMES ********************************************
***********************************************************************************************************/
public static final String PARAM_FORMAT = "format";
/***********************************************************************************************************
************************************************ ARGUMENTS VALUES ********************************************
***********************************************************************************************************/
public static final String VALUE_FORMAT = "json";
/*********************************************************************************************************** /***********************************************************************************************************
************************************************ STATUS CODES ********************************************* ************************************************ STATUS CODES *********************************************
***********************************************************************************************************/ ***********************************************************************************************************/
@ -171,6 +185,7 @@ public class HttpConstants {
public static final int HTTP_LOCKED = 423; public static final int HTTP_LOCKED = 423;
// 424 Failed Dependency (WebDAV - RFC 2518) // 424 Failed Dependency (WebDAV - RFC 2518)
public static final int HTTP_FAILED_DEPENDENCY = 424; public static final int HTTP_FAILED_DEPENDENCY = 424;
public static final int HTTP_TOO_EARLY = 425;
/** /**
* 5xx Client Error * 5xx Client Error
@ -204,4 +219,4 @@ public class HttpConstants {
* Default timeout for establishing a connection * Default timeout for establishing a connection
*/ */
public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
} }

View File

@ -41,13 +41,13 @@ object LogBuilder {
enum class NetworkPetition { enum class NetworkPetition {
REQUEST, RESPONSE; REQUEST, RESPONSE;
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT) override fun toString(): String = super.toString().lowercase(Locale.ROOT)
} }
enum class NetworkNode { enum class NetworkNode {
INFO, HEADER, BODY; INFO, HEADER, BODY;
override fun toString(): String = super.toString().toLowerCase(Locale.ROOT) override fun toString(): String = super.toString().lowercase(Locale.ROOT)
} }
/** /**

View File

@ -51,7 +51,7 @@ class LogInterceptor : Interceptor {
val request = chain.request().also { val request = chain.request().also {
val requestId = it.headers[OC_X_REQUEST_ID] val requestId = it.headers[OC_X_REQUEST_ID]
logHttp(REQUEST, INFO, requestId, "Type: ${it.method} URL: ${it.url}") logHttp(REQUEST, INFO, requestId, "Method: ${it.method} URL: ${it.url}")
logHeaders(requestId, it.headers, REQUEST) logHeaders(requestId, it.headers, REQUEST)
logRequestBody(requestId, it.body) logRequestBody(requestId, it.body)
} }
@ -64,7 +64,7 @@ class LogInterceptor : Interceptor {
RESPONSE, RESPONSE,
INFO, INFO,
requestId, requestId,
"Code: ${it.code} Message: ${it.message} IsSuccessful: ${it.isSuccessful}" "Method: ${request.method} URL: ${request.url} Code: ${it.code} Message: ${it.message}"
) )
logHeaders(requestId, it.headers, RESPONSE) logHeaders(requestId, it.headers, RESPONSE)
logResponseBody(requestId, it.body) logResponseBody(requestId, it.body)

View File

@ -1,3 +1,27 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods package com.owncloud.android.lib.common.http.methods
import com.owncloud.android.lib.common.http.HttpClient import com.owncloud.android.lib.common.http.HttpClient
@ -14,23 +38,41 @@ import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
abstract class HttpBaseMethod constructor(url: URL) { abstract class HttpBaseMethod constructor(url: URL) {
var okHttpClient: OkHttpClient
var httpUrl: HttpUrl = url.toHttpUrlOrNull() ?: throw MalformedURLException() var httpUrl: HttpUrl = url.toHttpUrlOrNull() ?: throw MalformedURLException()
var request: Request var request: Request
var followPermanentRedirects = false
abstract var response: Response abstract var response: Response
var call: Call? = null var call: Call? = null
var followRedirects: Boolean = true
var retryOnConnectionFailure: Boolean = true
var connectionTimeoutVal: Long? = null
var connectionTimeoutUnit: TimeUnit? = null
var readTimeoutVal: Long? = null
private set
var readTimeoutUnit: TimeUnit? = null
private set
init { init {
okHttpClient = HttpClient.getOkHttpClient()
request = Request.Builder() request = Request.Builder()
.url(httpUrl) .url(httpUrl)
.build() .build()
} }
@Throws(Exception::class) @Throws(Exception::class)
open fun execute(): Int { open fun execute(httpClient: HttpClient): Int {
return onExecute() val okHttpClient = httpClient.okHttpClient.newBuilder().apply {
retryOnConnectionFailure(retryOnConnectionFailure)
followRedirects(followRedirects)
readTimeoutUnit?.let { unit ->
readTimeoutVal?.let { readTimeout(it, unit) }
}
connectionTimeoutUnit?.let { unit ->
connectionTimeoutVal?.let { connectTimeout(it, unit) }
}
}.build()
return onExecute(okHttpClient)
} }
open fun setUrl(url: HttpUrl) { open fun setUrl(url: HttpUrl) {
@ -99,6 +141,11 @@ abstract class HttpBaseMethod constructor(url: URL) {
return response.body?.byteStream() return response.body?.byteStream()
} }
/**
* returns the final url after following the last redirect.
*/
open fun getFinalUrl() = response.request.url
/************************* /*************************
*** Connection Params *** *** Connection Params ***
*************************/ *************************/
@ -107,31 +154,19 @@ abstract class HttpBaseMethod constructor(url: URL) {
// Setter // Setter
////////////////////////////// //////////////////////////////
// Connection parameters // Connection parameters
open fun setRetryOnConnectionFailure(retryOnConnectionFailure: Boolean) {
okHttpClient = okHttpClient.newBuilder()
.retryOnConnectionFailure(retryOnConnectionFailure)
.build()
}
open fun setReadTimeout(readTimeout: Long, timeUnit: TimeUnit) { open fun setReadTimeout(readTimeout: Long, timeUnit: TimeUnit) {
okHttpClient = okHttpClient.newBuilder() readTimeoutVal = readTimeout
.readTimeout(readTimeout, timeUnit) readTimeoutUnit = timeUnit
.build()
} }
open fun setConnectionTimeout( open fun setConnectionTimeout(
connectionTimeout: Long, connectionTimeout: Long,
timeUnit: TimeUnit timeUnit: TimeUnit
) { ) {
okHttpClient = okHttpClient.newBuilder() connectionTimeoutVal = connectionTimeout
.readTimeout(connectionTimeout, timeUnit) connectionTimeoutUnit = timeUnit
.build()
}
open fun setFollowRedirects(followRedirects: Boolean) {
okHttpClient = okHttpClient.newBuilder()
.followRedirects(followRedirects)
.build()
} }
/************ /************
@ -148,5 +183,5 @@ abstract class HttpBaseMethod constructor(url: URL) {
// For override // For override
////////////////////////////// //////////////////////////////
@Throws(Exception::class) @Throws(Exception::class)
protected abstract fun onExecute(): Int protected abstract fun onExecute(okHttpClient: OkHttpClient): Int
} }

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.nonwebdav package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@ -33,10 +34,10 @@ import java.net.URL
*/ */
class DeleteMethod(url: URL) : HttpMethod(url) { class DeleteMethod(url: URL) : HttpMethod(url) {
@Throws(IOException::class) @Throws(IOException::class)
override fun onExecute(): Int { override fun onExecute(okHttpClient: OkHttpClient): Int {
request = request.newBuilder() request = request.newBuilder()
.delete() .delete()
.build() .build()
return super.onExecute() return super.onExecute(okHttpClient)
} }
} }

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.nonwebdav package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@ -33,10 +34,10 @@ import java.net.URL
*/ */
class GetMethod(url: URL) : HttpMethod(url) { class GetMethod(url: URL) : HttpMethod(url) {
@Throws(IOException::class) @Throws(IOException::class)
override fun onExecute(): Int { override fun onExecute(okHttpClient: OkHttpClient): Int {
request = request.newBuilder() request = request.newBuilder()
.get() .get()
.build() .build()
return super.onExecute() return super.onExecute(okHttpClient)
} }
} }

View File

@ -24,6 +24,7 @@
package com.owncloud.android.lib.common.http.methods.nonwebdav package com.owncloud.android.lib.common.http.methods.nonwebdav
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod import com.owncloud.android.lib.common.http.methods.HttpBaseMethod
import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import java.net.URL import java.net.URL
@ -38,7 +39,7 @@ abstract class HttpMethod(
override lateinit var response: Response override lateinit var response: Response
public override fun onExecute(): Int { public override fun onExecute(okHttpClient: OkHttpClient): Int {
call = okHttpClient.newCall(request) call = okHttpClient.newCall(request)
call?.let { response = it.execute() } call?.let { response = it.execute() }
return super.statusCode return super.statusCode

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.nonwebdav package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@ -37,10 +38,10 @@ class PostMethod(
private val postRequestBody: RequestBody private val postRequestBody: RequestBody
) : HttpMethod(url) { ) : HttpMethod(url) {
@Throws(IOException::class) @Throws(IOException::class)
override fun onExecute(): Int { override fun onExecute(okHttpClient: OkHttpClient): Int {
request = request.newBuilder() request = request.newBuilder()
.post(postRequestBody) .post(postRequestBody)
.build() .build()
return super.onExecute() return super.onExecute(okHttpClient)
} }
} }

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.nonwebdav package com.owncloud.android.lib.common.http.methods.nonwebdav
import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
@ -37,10 +38,10 @@ class PutMethod(
private val putRequestBody: RequestBody private val putRequestBody: RequestBody
) : HttpMethod(url) { ) : HttpMethod(url) {
@Throws(IOException::class) @Throws(IOException::class)
override fun onExecute(): Int { override fun onExecute(okHttpClient: OkHttpClient): Int {
request = request.newBuilder() request = request.newBuilder()
.put(putRequestBody) .put(putRequestBody)
.build() .build()
return super.onExecute() return super.onExecute(okHttpClient)
} }
} }

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response import okhttp3.Response
import java.net.URL import java.net.URL
@ -35,10 +36,10 @@ import java.net.URL
class CopyMethod( class CopyMethod(
val url: URL, val url: URL,
private val destinationUrl: String, private val destinationUrl: String,
private val forceOverride: Boolean val forceOverride: Boolean = false
) : DavMethod(url) { ) : DavMethod(url) {
@Throws(Exception::class) @Throws(Exception::class)
public override fun onExecute(): Int { public override fun onDavExecute(davResource: DavOCResource): Int {
davResource.copy( davResource.copy(
destinationUrl, destinationUrl,
forceOverride, forceOverride,

View File

@ -29,14 +29,11 @@ import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.dav4jvm.exception.RedirectException import at.bitfire.dav4jvm.exception.RedirectException
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod import com.owncloud.android.lib.common.http.methods.HttpBaseMethod
import okhttp3.HttpUrl import okhttp3.OkHttpClient
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Protocol import okhttp3.Protocol
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.util.concurrent.TimeUnit
/** /**
* Wrapper to perform WebDAV (dav4android) calls * Wrapper to perform WebDAV (dav4android) calls
@ -44,27 +41,25 @@ import java.util.concurrent.TimeUnit
* @author David González Verdugo * @author David González Verdugo
*/ */
abstract class DavMethod protected constructor(url: URL) : HttpBaseMethod(url) { abstract class DavMethod protected constructor(url: URL) : HttpBaseMethod(url) {
protected var davResource: DavOCResource
override lateinit var response: Response override lateinit var response: Response
private var davResource: DavOCResource? = null
init {
val httpUrl = url.toHttpUrlOrNull() ?: throw MalformedURLException()
davResource = DavOCResource(
okHttpClient,
httpUrl,
log
)
}
override fun abort() { override fun abort() {
davResource.cancelCall() davResource?.cancelCall()
} }
protected abstract fun onDavExecute(davResource: DavOCResource): Int
@Throws(Exception::class) @Throws(Exception::class)
override fun execute(): Int { override fun onExecute(okHttpClient: OkHttpClient): Int {
return try { return try {
onExecute() davResource = DavOCResource(
okHttpClient.newBuilder().followRedirects(false).build(),
httpUrl,
log
)
onDavExecute(davResource!!)
} catch (httpException: HttpException) { } catch (httpException: HttpException) {
// Modify responses with information gathered from exceptions // Modify responses with information gathered from exceptions
if (httpException is RedirectException) { if (httpException is RedirectException) {
@ -91,71 +86,12 @@ abstract class DavMethod protected constructor(url: URL) : HttpBaseMethod(url) {
} }
} }
//////////////////////////////
// Setter
//////////////////////////////
// Connection parameters
override fun setReadTimeout(readTimeout: Long, timeUnit: TimeUnit) {
super.setReadTimeout(readTimeout, timeUnit)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setConnectionTimeout(
connectionTimeout: Long,
timeUnit: TimeUnit
) {
super.setConnectionTimeout(connectionTimeout, timeUnit)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setFollowRedirects(followRedirects: Boolean) {
super.setFollowRedirects(followRedirects)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setUrl(url: HttpUrl) {
super.setUrl(url)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override fun setRequestHeader(name: String, value: String) {
super.setRequestHeader(name, value)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
////////////////////////////// //////////////////////////////
// Getter // Getter
////////////////////////////// //////////////////////////////
override fun setRetryOnConnectionFailure(retryOnConnectionFailure: Boolean) {
super.setRetryOnConnectionFailure(retryOnConnectionFailure)
davResource = DavOCResource(
okHttpClient,
request.url,
log
)
}
override val isAborted: Boolean override val isAborted: Boolean
get() = davResource.isCallAborted() get() = davResource?.isCallAborted() ?: false
} }

View File

@ -24,12 +24,36 @@
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.Property import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.PropertyUtils.getAllPropSet
import at.bitfire.dav4jvm.PropertyUtils.getQuotaPropset import at.bitfire.dav4jvm.PropertyUtils.getQuotaPropset
import at.bitfire.dav4jvm.property.CreationDate
import at.bitfire.dav4jvm.property.DisplayName
import at.bitfire.dav4jvm.property.GetContentLength
import at.bitfire.dav4jvm.property.GetContentType
import at.bitfire.dav4jvm.property.GetETag
import at.bitfire.dav4jvm.property.GetLastModified
import at.bitfire.dav4jvm.property.OCId
import at.bitfire.dav4jvm.property.OCPermissions
import at.bitfire.dav4jvm.property.OCPrivatelink
import at.bitfire.dav4jvm.property.OCSize
import at.bitfire.dav4jvm.property.ResourceType
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
object DavUtils { object DavUtils {
@JvmStatic val allPropset: Array<Property.Name> @JvmStatic val allPropSet: Array<Property.Name>
get() = getAllPropSet() get() = arrayOf(
DisplayName.NAME,
GetContentType.NAME,
ResourceType.NAME,
GetContentLength.NAME,
GetLastModified.NAME,
CreationDate.NAME,
GetETag.NAME,
OCPermissions.NAME,
OCId.NAME,
OCSize.NAME,
OCPrivatelink.NAME,
OCShareTypes.NAME,
)
val quotaPropSet: Array<Property.Name> val quotaPropSet: Array<Property.Name>
get() = getQuotaPropset() get() = getQuotaPropset()

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response import okhttp3.Response
import java.net.URL import java.net.URL
@ -34,7 +35,7 @@ import java.net.URL
*/ */
class MkColMethod(url: URL) : DavMethod(url) { class MkColMethod(url: URL) : DavMethod(url) {
@Throws(Exception::class) @Throws(Exception::class)
public override fun onExecute(): Int { public override fun onDavExecute(davResource: DavOCResource): Int {
davResource.mkCol( davResource.mkCol(
xmlBody = null, xmlBody = null,
listOfHeaders = super.getRequestHeadersAsHashMap() listOfHeaders = super.getRequestHeadersAsHashMap()

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import okhttp3.Response import okhttp3.Response
import java.net.URL import java.net.URL
@ -35,10 +36,10 @@ import java.net.URL
class MoveMethod( class MoveMethod(
url: URL, url: URL,
private val destinationUrl: String, private val destinationUrl: String,
private val forceOverride: Boolean val forceOverride: Boolean = false
) : DavMethod(url) { ) : DavMethod(url) {
@Throws(Exception::class) @Throws(Exception::class)
public override fun onExecute(): Int { override fun onDavExecute(davResource: DavOCResource): Int {
davResource.move( davResource.move(
destinationUrl, destinationUrl,
forceOverride, forceOverride,

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import at.bitfire.dav4jvm.Property import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.Response.HrefRelation import at.bitfire.dav4jvm.Response.HrefRelation
@ -47,12 +48,12 @@ class PropfindMethod(
private set private set
@Throws(IOException::class, DavException::class) @Throws(IOException::class, DavException::class)
public override fun onExecute(): Int { public override fun onDavExecute(davResource: DavOCResource): Int {
davResource.propfind( davResource.propfind(
depth = depth, depth = depth,
reqProp = *propertiesToRequest, reqProp = propertiesToRequest,
listOfHeaders = super.getRequestHeadersAsHashMap(), listOfHeaders = super.getRequestHeadersAsHashMap(),
callback = { response: Response, hrefRelation: HrefRelation? -> callback = { response: Response, hrefRelation: HrefRelation ->
when (hrefRelation) { when (hrefRelation) {
HrefRelation.MEMBER -> members.add(response) HrefRelation.MEMBER -> members.add(response)
HrefRelation.SELF -> this.root = response HrefRelation.SELF -> this.root = response

View File

@ -23,6 +23,7 @@
*/ */
package com.owncloud.android.lib.common.http.methods.webdav package com.owncloud.android.lib.common.http.methods.webdav
import at.bitfire.dav4jvm.DavOCResource
import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.exception.HttpException
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import okhttp3.RequestBody import okhttp3.RequestBody
@ -39,7 +40,7 @@ class PutMethod(
private val putRequestBody: RequestBody private val putRequestBody: RequestBody
) : DavMethod(url) { ) : DavMethod(url) {
@Throws(IOException::class, HttpException::class) @Throws(IOException::class, HttpException::class)
public override fun onExecute(): Int { public override fun onDavExecute(davResource: DavOCResource): Int {
davResource.put( davResource.put(
putRequestBody, putRequestBody,
super.getRequestHeader(HttpConstants.IF_MATCH_HEADER), super.getRequestHeader(HttpConstants.IF_MATCH_HEADER),

View File

@ -0,0 +1,44 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods.webdav.properties
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.XmlUtils
import org.xmlpull.v1.XmlPullParser
class OCShareTypes : ShareTypeListProperty() {
class Factory : ShareTypeListProperty.Factory() {
override fun create(parser: XmlPullParser) =
create(parser, OCShareTypes())
override fun getName(): Property.Name = NAME
}
companion object {
@JvmField
val NAME = Property.Name(XmlUtils.NS_OWNCLOUD, "share-types")
}
}

View File

@ -0,0 +1,46 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.http.methods.webdav.properties
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.PropertyFactory
import at.bitfire.dav4jvm.XmlUtils
import org.xmlpull.v1.XmlPullParser
import java.util.LinkedList
abstract class ShareTypeListProperty : Property {
val shareTypes = LinkedList<String>()
override fun toString() = "share types =[" + shareTypes.joinToString(", ") + "]"
abstract class Factory : PropertyFactory {
fun create(parser: XmlPullParser, list: ShareTypeListProperty): ShareTypeListProperty {
XmlUtils.readTextPropertyList(parser, Property.Name(XmlUtils.NS_OWNCLOUD, "share-type"), list.shareTypes)
return list
}
}
}

View File

@ -1,115 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network;
import okhttp3.MediaType;
import okio.BufferedSink;
import timber.log.Timber;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
/**
* A Request body that represents a file chunk and include information about the progress when uploading it
*
* @author David González Verdugo
*/
public class ChunkFromFileRequestBody extends FileRequestBody {
private final FileChannel mChannel;
private final long mChunkSize;
private long mOffset;
private long mTransferred;
private ByteBuffer mBuffer = ByteBuffer.allocate(4096);
public ChunkFromFileRequestBody(File file, MediaType contentType, FileChannel channel, long chunkSize) {
super(file, contentType);
if (channel == null) {
throw new IllegalArgumentException("File may not be null");
}
if (chunkSize <= 0) {
throw new IllegalArgumentException("Chunk size must be greater than zero");
}
this.mChannel = channel;
this.mChunkSize = chunkSize;
mOffset = 0;
mTransferred = 0;
}
@Override
public long contentLength() {
try {
return Math.min(mChunkSize, mChannel.size() - mChannel.position());
} catch (IOException e) {
return mChunkSize;
}
}
@Override
public void writeTo(BufferedSink sink) {
int readCount;
Iterator<OnDatatransferProgressListener> it;
try {
mChannel.position(mOffset);
long size = mFile.length();
if (size == 0) {
size = -1;
}
long maxCount = Math.min(mOffset + mChunkSize, mChannel.size());
while (mChannel.position() < maxCount) {
readCount = mChannel.read(mBuffer);
sink.getBuffer().write(mBuffer.array(), 0, readCount);
sink.flush();
mBuffer.clear();
if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
mTransferred += readCount;
}
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readCount, mTransferred, size, mFile.getAbsolutePath());
}
}
}
Timber.v("Chunk with size " + mChunkSize + " written in request body");
} catch (Exception exception) {
Timber.e(exception);
}
}
public void setOffset(long offset) {
this.mOffset = offset;
}
}

View File

@ -0,0 +1,92 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSystemOperation.Companion.CHUNK_SIZE
import okhttp3.MediaType
import okio.BufferedSink
import timber.log.Timber
import java.io.File
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
/**
* A Request body that represents a file chunk and include information about the progress when uploading it
*
* @author David González Verdugo
*/
class ChunkFromFileRequestBody(
file: File,
contentType: MediaType?,
private val channel: FileChannel,
private val chunkSize: Long = CHUNK_SIZE
) : FileRequestBody(file, contentType) {
private var offset: Long = 0
private var alreadyTransferred: Long = 0
private val buffer = ByteBuffer.allocate(4_096)
init {
require(chunkSize > 0) { "Chunk size must be greater than zero" }
}
override fun contentLength(): Long {
return chunkSize.coerceAtMost(channel.size() - channel.position())
}
override fun writeTo(sink: BufferedSink) {
var readCount: Int
var iterator: Iterator<OnDatatransferProgressListener>
try {
channel.position(offset)
val maxCount = (offset + chunkSize).coerceAtMost(channel.size())
while (channel.position() < maxCount) {
readCount = channel.read(buffer)
val bytesToWriteInBuffer = readCount.toLong().coerceAtMost(file.length() - alreadyTransferred).toInt()
sink.buffer.write(buffer.array(), 0, bytesToWriteInBuffer)
sink.flush()
buffer.clear()
if (alreadyTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
alreadyTransferred += readCount.toLong()
}
synchronized(dataTransferListeners) {
iterator = dataTransferListeners.iterator()
while (iterator.hasNext()) {
iterator.next().onTransferProgress(readCount.toLong(), alreadyTransferred, file.length(), file.absolutePath)
}
}
}
} catch (exception: Exception) {
Timber.e(exception, "Transferred " + alreadyTransferred + " bytes from a total of " + file.length())
}
}
fun setOffset(newOffset: Long) {
offset = newOffset
}
}

View File

@ -0,0 +1,117 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import android.content.ContentResolver
import android.net.Uri
import android.provider.OpenableColumns
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Source
import okio.source
import timber.log.Timber
import java.io.IOException
class ContentUriRequestBody(
private val contentResolver: ContentResolver,
private val contentUri: Uri
) : RequestBody(), ProgressiveDataTransferer {
private val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
val fileSize: Long = contentResolver.query(contentUri, null, null, null, null)?.use { cursor ->
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
cursor.getLong(sizeIndex)
} ?: -1
override fun contentType(): MediaType? {
val contentType = contentResolver.getType(contentUri) ?: return null
return contentType.toMediaTypeOrNull()
}
override fun contentLength(): Long {
return fileSize
}
override fun writeTo(sink: BufferedSink) {
val inputStream = contentResolver.openInputStream(contentUri)
?: throw IOException("Couldn't open content URI for reading: $contentUri")
val previousTime = System.currentTimeMillis()
sink.writeAndUpdateProgress(inputStream.source())
inputStream.source().close()
val laterTime = System.currentTimeMillis()
Timber.d("Difference - ${laterTime - previousTime} milliseconds")
}
private fun BufferedSink.writeAndUpdateProgress(source: Source) {
var iterator: Iterator<OnDatatransferProgressListener>
try {
var totalBytesRead = 0L
var read: Long
while (source.read(this.buffer, BYTES_TO_READ).also { read = it } != -1L) {
totalBytesRead += read
this.flush()
synchronized(dataTransferListeners) {
iterator = dataTransferListeners.iterator()
while (iterator.hasNext()) {
iterator.next().onTransferProgress(read, totalBytesRead, fileSize, contentUri.toString())
}
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.add(listener)
}
}
override fun addDatatransferProgressListeners(listeners: MutableCollection<OnDatatransferProgressListener>) {
synchronized(dataTransferListeners) {
dataTransferListeners.addAll(listeners)
}
}
override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.remove(listener)
}
}
companion object {
private const val BYTES_TO_READ = 4_096L
}
}

View File

@ -1,119 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import timber.log.Timber;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* A Request body that represents a file and include information about the progress when uploading it
*
* @author David González Verdugo
*/
public class FileRequestBody extends RequestBody implements ProgressiveDataTransferer {
final Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
protected File mFile;
private MediaType mContentType;
public FileRequestBody(File file, MediaType contentType) {
mFile = file;
mContentType = contentType;
}
@Override
public boolean isOneShot() {
return true;
}
@Override
public MediaType contentType() {
return mContentType;
}
@Override
public long contentLength() {
return mFile.length();
}
@Override
public void writeTo(BufferedSink sink) {
Source source;
Iterator<OnDatatransferProgressListener> it;
try {
source = Okio.source(mFile);
long transferred = 0;
long read;
while ((read = source.read(sink.buffer(), 4096)) != -1) {
transferred += read;
sink.flush();
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(read, transferred, mFile.length(), mFile.getAbsolutePath());
}
}
}
Timber.d("File with name " + mFile.getName() + " and size " + mFile.length() + " written in request body");
} catch (Exception e) {
Timber.e(e);
}
}
@Override
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
@Override
public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.addAll(listeners);
}
}
@Override
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
}

View File

@ -0,0 +1,97 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.network
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.Source
import okio.source
import timber.log.Timber
import java.io.File
import java.util.HashSet
/**
* A Request body that represents a file and include information about the progress when uploading it
*
* @author David González Verdugo
*/
open class FileRequestBody(
val file: File,
private val contentType: MediaType?,
) : RequestBody(), ProgressiveDataTransferer {
val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
override fun isOneShot(): Boolean = true
override fun contentType(): MediaType? = contentType
override fun contentLength(): Long = file.length()
override fun writeTo(sink: BufferedSink) {
val source: Source
var it: Iterator<OnDatatransferProgressListener>
try {
source = file.source()
var transferred: Long = 0
var read: Long
while (source.read(sink.buffer, BYTES_TO_READ).also { read = it } != -1L) {
transferred += read
sink.flush()
synchronized(dataTransferListeners) {
it = dataTransferListeners.iterator()
while (it.hasNext()) {
it.next().onTransferProgress(read, transferred, file.length(), file.absolutePath)
}
}
}
Timber.d("File with name ${file.name} and size ${file.length()} written in request body")
} catch (e: Exception) {
Timber.e(e)
}
}
override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.add(listener)
}
}
override fun addDatatransferProgressListeners(listeners: Collection<OnDatatransferProgressListener>) {
synchronized(dataTransferListeners) {
dataTransferListeners.addAll(listeners)
}
}
override fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) {
dataTransferListeners.remove(listener)
}
}
companion object {
private const val BYTES_TO_READ = 4_096L
}
}

View File

@ -1,3 +1,27 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.operations; package com.owncloud.android.lib.common.operations;
import android.accounts.Account; import android.accounts.Account;
@ -135,7 +159,7 @@ public abstract class RemoteOperation<T> implements Runnable {
if (mAccount != null && mContext != null) { if (mAccount != null && mContext != null) {
OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
mClient = SingleSessionManager.getDefaultSingleton(). mClient = SingleSessionManager.getDefaultSingleton().
getClientFor(ocAccount, mContext); getClientFor(ocAccount, mContext, SingleSessionManager.getConnectionValidator());
} else { } else {
throw new IllegalStateException("Trying to run a remote operation " + throw new IllegalStateException("Trying to run a remote operation " +
"asynchronously with no client and no chance to create one (no account)"); "asynchronously with no client and no chance to create one (no account)");
@ -265,4 +289,4 @@ public abstract class RemoteOperation<T> implements Runnable {
mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2022 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -34,6 +34,7 @@ import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; import com.owncloud.android.lib.common.http.methods.HttpBaseMethod;
import com.owncloud.android.lib.common.network.CertificateCombinedException; import com.owncloud.android.lib.common.network.CertificateCombinedException;
import okhttp3.Headers; import okhttp3.Headers;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.JSONException; import org.json.JSONException;
import timber.log.Timber; import timber.log.Timber;
@ -45,6 +46,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -59,13 +61,15 @@ public class RemoteOperationResult<T>
* Generated - should be refreshed every time the class changes!! * Generated - should be refreshed every time the class changes!!
*/ */
private static final long serialVersionUID = 4968939884332372230L; private static final long serialVersionUID = 4968939884332372230L;
private static final String LOCATION = "location";
private static final String WWW_AUTHENTICATE = "www-authenticate";
private boolean mSuccess = false; private boolean mSuccess = false;
private int mHttpCode = -1; private int mHttpCode = -1;
private String mHttpPhrase = null; private String mHttpPhrase = null;
private Exception mException = null; private Exception mException = null;
private ResultCode mCode = ResultCode.UNKNOWN_ERROR; private ResultCode mCode = ResultCode.UNKNOWN_ERROR;
private String mRedirectedLocation; private String mRedirectedLocation = "";
private List<String> mAuthenticate = new ArrayList<>(); private List<String> mAuthenticate = new ArrayList<>();
private String mLastPermanentLocation = null; private String mLastPermanentLocation = null;
private T mData = null; private T mData = null;
@ -112,6 +116,14 @@ public class RemoteOperationResult<T>
*/ */
public RemoteOperationResult(Exception e) { public RemoteOperationResult(Exception e) {
mException = e; mException = e;
//TODO: Do propper exception handling and remove this
Timber.e("---------------------------------" +
"\nCreate RemoteOperationResult from exception." +
"\n Message: %s" +
"\n Stacktrace: %s" +
"\n---------------------------------",
ExceptionUtils.getMessage(e),
ExceptionUtils.getStackTrace(e));
if (e instanceof OperationCancelledException) { if (e instanceof OperationCancelledException) {
mCode = ResultCode.CANCELLED; mCode = ResultCode.CANCELLED;
@ -155,7 +167,10 @@ public class RemoteOperationResult<T>
} else if (e instanceof FileNotFoundException) { } else if (e instanceof FileNotFoundException) {
mCode = ResultCode.LOCAL_FILE_NOT_FOUND; mCode = ResultCode.LOCAL_FILE_NOT_FOUND;
} else { } else if (e instanceof ProtocolException) {
mCode = ResultCode.NETWORK_ERROR;
}
else {
mCode = ResultCode.UNKNOWN_ERROR; mCode = ResultCode.UNKNOWN_ERROR;
} }
} }
@ -226,6 +241,10 @@ public class RemoteOperationResult<T>
httpMethod.getResponseBodyAsString(), httpMethod.getResponseBodyAsString(),
ResultCode.SPECIFIC_METHOD_NOT_ALLOWED ResultCode.SPECIFIC_METHOD_NOT_ALLOWED
); );
break;
case HttpConstants.HTTP_TOO_EARLY:
mCode = ResultCode.TOO_EARLY;
break;
default: default:
break; break;
} }
@ -248,11 +267,11 @@ public class RemoteOperationResult<T>
this(httpCode, httpPhrase); this(httpCode, httpPhrase);
if (headers != null) { if (headers != null) {
for (Map.Entry<String, List<String>> header : headers.toMultimap().entrySet()) { for (Map.Entry<String, List<String>> header : headers.toMultimap().entrySet()) {
if ("location".equals(header.getKey().toLowerCase())) { if (LOCATION.equalsIgnoreCase(header.getKey())) {
mRedirectedLocation = header.getValue().get(0); mRedirectedLocation = header.getValue().get(0);
continue; continue;
} }
if ("www-authenticate".equals(header.getKey().toLowerCase())) { if (WWW_AUTHENTICATE.equalsIgnoreCase(header.getKey())) {
for (String value: header.getValue()) { for (String value: header.getValue()) {
mAuthenticate.add(value.toLowerCase()); mAuthenticate.add(value.toLowerCase());
} }
@ -321,7 +340,7 @@ public class RemoteOperationResult<T>
mHttpPhrase = errorMessage; mHttpPhrase = errorMessage;
} }
} catch (Exception e) { } catch (Exception e) {
Timber.w("Error reading exception from server: %s", e.getMessage()); Timber.w("Error reading exception from server: %s\nTrace: %s", e.getMessage(), ExceptionUtils.getStackTrace(e));
// mCode stays as set in this(success, httpCode, headers) // mCode stays as set in this(success, httpCode, headers)
} }
} }
@ -572,6 +591,8 @@ public class RemoteOperationResult<T>
SPECIFIC_SERVICE_UNAVAILABLE, SPECIFIC_SERVICE_UNAVAILABLE,
SPECIFIC_UNSUPPORTED_MEDIA_TYPE, SPECIFIC_UNSUPPORTED_MEDIA_TYPE,
SPECIFIC_METHOD_NOT_ALLOWED, SPECIFIC_METHOD_NOT_ALLOWED,
SPECIFIC_BAD_REQUEST SPECIFIC_BAD_REQUEST,
TOO_EARLY,
NETWORK_ERROR,
} }
} }

View File

@ -22,20 +22,8 @@
* *
*/ */
package com.owncloud.android.lib.resources.shares; package com.owncloud.android.lib.common.utils
/** fun Any.isOneOf(vararg values: Any): Boolean {
* Contains Constants for Share Operation return this in values
*
* @author masensio
* @author David González Verdugo
*/
public class ShareUtils {
// OCS Route
public static final String SHARING_API_PATH = "ocs/v2.php/apps/files_sharing/api/v1/shares";
// String to build the link with the token of a share:
public static final String SHARING_LINK_PATH = "/index.php/s/";
} }

View File

@ -1,3 +1,27 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.common.utils package com.owncloud.android.lib.common.utils
import info.hannes.timber.FileLoggingTree import info.hannes.timber.FileLoggingTree
@ -9,7 +33,7 @@ object LoggingHelper {
fun startLogging(directory: File, storagePath: String) { fun startLogging(directory: File, storagePath: String) {
fileLoggingTree()?.let { fileLoggingTree()?.let {
Timber.forest().drop(Timber.forest().indexOf(it)) Timber.uproot(it)
} }
if (!directory.exists()) if (!directory.exists())
directory.mkdirs() directory.mkdirs()
@ -18,7 +42,7 @@ object LoggingHelper {
fun stopLogging() { fun stopLogging() {
fileLoggingTree()?.let { fileLoggingTree()?.let {
Timber.forest().drop(Timber.forest().indexOf(it)) Timber.uproot(it)
} }
} }
} }

View File

@ -36,7 +36,7 @@ data class CommonOcsResponse<T>(
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class OCSResponse<T>( data class OCSResponse<T>(
val meta: MetaData, val meta: MetaData,
val data: T val data: T?
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)

View File

@ -1,21 +1,27 @@
/** /* ownCloud Android Library is available under MIT license
* ownCloud Android client application * Copyright (C) 2022 ownCloud GmbH.
* *
* @author David González Verdugo * @author David González Verdugo
* *
* Copyright (C) 2020 ownCloud GmbH. * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* *
* This program is free software: you can redistribute it and/or modify * The above copyright notice and this permission notice shall be included in
* it under the terms of the GNU General Public License version 2, * all copies or substantial portions of the Software.
* as published by the Free Software Foundation. *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.owncloud.android.lib.resources package com.owncloud.android.lib.resources

View File

@ -0,0 +1,108 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.FormBody
import okhttp3.RequestBody
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
class CreateRemoteFileWithAppProviderOperation(
private val createFileWithAppProviderEndpoint: String,
private val parentContainerId: String,
private val filename: String,
) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try {
val createFileWithAppProviderRequestBody = CreateFileWithAppProviderParams(parentContainerId, filename)
.toRequestBody()
val stringUrl = client.baseUri.toString() + WebdavUtils.encodePath(createFileWithAppProviderEndpoint)
val postMethod = PostMethod(URL(stringUrl), createFileWithAppProviderRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(postMethod)
Timber.d("Create file $filename with app provider in folder with ID $parentContainerId - $status${if (!isSuccess(status)) "(FAIL)" else ""}")
if (isSuccess(status)) RemoteOperationResult<String>(ResultCode.OK).apply {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<CreateFileWithAppProviderResponse> = moshi.adapter(CreateFileWithAppProviderResponse::class.java)
data = postMethod.getResponseBodyAsString()?.let { adapter.fromJson(it)!!.fileId }
}
else RemoteOperationResult<String>(postMethod).apply { data = "" }
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
Timber.e(e, "Create file $filename with app provider in folder with ID $parentContainerId failed")
result
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class CreateFileWithAppProviderParams(
val parentContainerId: String,
val filename: String,
) {
fun toRequestBody(): RequestBody =
FormBody.Builder()
.add(PARAM_PARENT_CONTAINER_ID, parentContainerId)
.add(PARAM_FILENAME, filename)
.build()
companion object {
const val PARAM_PARENT_CONTAINER_ID = "parent_container_id"
const val PARAM_FILENAME = "filename"
}
}
@JsonClass(generateAdapter = true)
data class CreateFileWithAppProviderResponse(
@Json(name = "file_id")
val fileId: String,
)
companion object {
private const val TIMEOUT: Long = 5_000
}
}

View File

@ -0,0 +1,78 @@
/* ownCloud Android Library is available under MIT license
* @author Abel García de Prada
*
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import timber.log.Timber
import java.net.URL
class GetRemoteAppRegistryOperation(private val appUrl: String?) : RemoteOperation<AppRegistryResponse>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<AppRegistryResponse> {
var result: RemoteOperationResult<AppRegistryResponse>
try {
val uriBuilder = client.baseUri.buildUpon().apply {
appendEncodedPath(appUrl)
}
val getMethod = GetMethod(URL(uriBuilder.build().toString()))
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (status == HttpConstants.HTTP_OK) {
Timber.d("Successful response $response")
// Parse the response
val moshi: Moshi = Moshi.Builder().build()
val adapter: JsonAdapter<AppRegistryResponse> = moshi.adapter(AppRegistryResponse::class.java)
val appRegistryResponse: AppRegistryResponse = response?.let { adapter.fromJson(it) } ?: AppRegistryResponse(value = emptyList())
result = RemoteOperationResult(OK)
result.data = appRegistryResponse
Timber.d("Get AppRegistry completed and parsed to ${result.data}")
} else {
result = RemoteOperationResult(getMethod)
Timber.e("Failed response while getting app registry from the server status code: $status; response message: $response")
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while getting app registry")
}
return result
}
}

View File

@ -0,0 +1,107 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.appregistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import okhttp3.FormBody
import okhttp3.RequestBody
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
class GetUrlToOpenInWebRemoteOperation(
private val openWithWebEndpoint: String,
private val fileId: String,
private val appName: String,
) : RemoteOperation<String>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
return try {
val openInWebRequestBody = OpenInWebParams(fileId, appName).toRequestBody()
val stringUrl =
client.baseUri.toString() + WebdavUtils.encodePath(openWithWebEndpoint)
val postMethod = PostMethod(URL(stringUrl), openInWebRequestBody).apply {
setReadTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(postMethod)
Timber.d("Open in web for file: $fileId - $status${if (!isSuccess(status)) "(FAIL)" else ""}")
if (isSuccess(status)) RemoteOperationResult<String>(ResultCode.OK).apply {
val moshi = Moshi.Builder().build()
val adapter: JsonAdapter<OpenInWebResponse> = moshi.adapter(OpenInWebResponse::class.java)
data = postMethod.getResponseBodyAsString()?.let { adapter.fromJson(it)!!.uri }
}
else RemoteOperationResult<String>(postMethod).apply { data = "" }
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
Timber.e(e, "Open in web for file: $fileId failed")
result
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
data class OpenInWebParams(
val fileId: String,
val appName: String,
) {
fun toRequestBody(): RequestBody =
FormBody.Builder()
.add(PARAM_FILE_ID, fileId)
.add(PARAM_APP_NAME, appName)
.build()
companion object {
const val PARAM_FILE_ID = "file_id"
const val PARAM_APP_NAME = "app_name"
}
}
@JsonClass(generateAdapter = true)
data class OpenInWebResponse(val uri: String)
companion object {
/**
* Maximum time to wait for a response from the server in milliseconds.
*/
private const val TIMEOUT = 5_000L
}
}

View File

@ -1,5 +1,7 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * @author Abel García de Prada
*
* Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -21,25 +23,34 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.resources.appregistry.responses
package com.owncloud.android.lib.resources.files.chunks; import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; @JsonClass(generateAdapter = true)
data class AppRegistryResponse(
@Json(name = "mime-types")
val value: List<AppRegistryMimeTypeResponse>
)
/** @JsonClass(generateAdapter = true)
* Remote operation performing the creation of a new folder to save chunks during an upload to the ownCloud server. data class AppRegistryMimeTypeResponse(
* @Json(name = "mime_type") val mimeType: String,
* @author David González Verdugo val ext: String? = null,
*/ @Json(name = "app_providers")
public class CreateRemoteChunkFolderOperation extends CreateRemoteFolderOperation { val appProviders: List<AppRegistryProviderResponse>,
/** val name: String? = null,
* Constructor val icon: String? = null,
* val description: String? = null,
* @param remotePath Full path to the new directory to create in the remote server. @Json(name = "allow_creation")
* @param createFullPath 'True' means that all the ancestor folders should be created. val allowCreation: Boolean? = null,
*/ @Json(name = "default_application")
public CreateRemoteChunkFolderOperation(String remotePath, boolean createFullPath) { val defaultApplication: String? = null
super(remotePath, createFullPath); )
createChunksFolder = true;
} @JsonClass(generateAdapter = true)
} data class AppRegistryProviderResponse(
val name: String,
val icon: String,
)

View File

@ -0,0 +1,44 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.appregistry.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
interface AppRegistryService : Service {
fun getAppRegistry(appUrl: String?): RemoteOperationResult<AppRegistryResponse>
fun getUrlToOpenInWeb(
openWebEndpoint: String,
fileId: String,
appName: String,
): RemoteOperationResult<String>
fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String,
): RemoteOperationResult<String>
}

View File

@ -0,0 +1,54 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.owncloud.android.lib.resources.appregistry.services
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.appregistry.CreateRemoteFileWithAppProviderOperation
import com.owncloud.android.lib.resources.appregistry.GetRemoteAppRegistryOperation
import com.owncloud.android.lib.resources.appregistry.GetUrlToOpenInWebRemoteOperation
import com.owncloud.android.lib.resources.appregistry.responses.AppRegistryResponse
class OCAppRegistryService(override val client: OwnCloudClient) : AppRegistryService {
override fun getAppRegistry(appUrl: String?): RemoteOperationResult<AppRegistryResponse> =
GetRemoteAppRegistryOperation(appUrl).execute(client)
override fun getUrlToOpenInWeb(openWebEndpoint: String, fileId: String, appName: String): RemoteOperationResult<String> =
GetUrlToOpenInWebRemoteOperation(
openWithWebEndpoint = openWebEndpoint,
fileId = fileId,
appName = appName
).execute(client)
override fun createFileWithAppProvider(
createFileWithAppProviderEndpoint: String,
parentContainerId: String,
filename: String
): RemoteOperationResult<String> =
CreateRemoteFileWithAppProviderOperation(
createFileWithAppProviderEndpoint = createFileWithAppProviderEndpoint,
parentContainerId = parentContainerId,
filename = filename,
).execute(client)
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,9 +25,8 @@ package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropset import com.owncloud.android.lib.common.http.methods.webdav.DavUtils.allPropSet
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.network.RedirectionPath
import com.owncloud.android.lib.common.network.WebdavUtils import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
@ -42,41 +41,29 @@ import java.util.concurrent.TimeUnit
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Abel García de Prada * @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* *
* @param remotePath Path to append to the URL owned by the client instance. * @param remotePath Path to append to the URL owned by the client instance.
* @param isUserLogged When `true`, the username won't be added at the end of the PROPFIND url since is not * @param isUserLoggedIn When `true`, the username won't be added at the end of the PROPFIND url since is not
* needed to check user credentials * needed to check user credentials
*/ */
class CheckPathExistenceRemoteOperation( class CheckPathExistenceRemoteOperation(
val remotePath: String? = "", val remotePath: String? = "",
val isUserLogged: Boolean val isUserLoggedIn: Boolean,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Boolean>() { ) : RemoteOperation<Boolean>() {
/**
* Gets the sequence of redirections followed during the execution of the operation.
*
* @return Sequence of redirections followed, if any, or NULL if the operation was not executed.
*/
var redirectionPath: RedirectionPath? = null
private set
override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> { override fun run(client: OwnCloudClient): RemoteOperationResult<Boolean> {
val previousFollowRedirects = client.followRedirects() val baseStringUrl = spaceWebDavUrl ?: if (isUserLoggedIn) client.userFilesWebDavUri.toString() else client.baseFilesWebDavUri.toString()
return try { val stringUrl = if (isUserLoggedIn) baseStringUrl + WebdavUtils.encodePath(remotePath) else baseStringUrl
val stringUrl =
if (isUserLogged) client.baseFilesWebDavUri.toString()
else client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(remotePath)
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropset).apply { return try {
val propFindMethod = PropfindMethod(URL(stringUrl), 0, allPropSet).apply {
setReadTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS) setReadTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS) setConnectionTimeout(TIMEOUT.toLong(), TimeUnit.SECONDS)
} }
client.setFollowRedirects(false) val status = client.executeHttpMethod(propFindMethod)
var status = client.executeHttpMethod(propFindMethod)
if (previousFollowRedirects) {
redirectionPath = client.followRedirection(propFindMethod)
status = redirectionPath?.lastStatus!!
}
/* PROPFIND method /* PROPFIND method
* 404 NOT FOUND: path doesn't exist, * 404 NOT FOUND: path doesn't exist,
* 207 MULTI_STATUS: path exists. * 207 MULTI_STATUS: path exists.
@ -91,19 +78,13 @@ class CheckPathExistenceRemoteOperation(
val result = RemoteOperationResult<Boolean>(e) val result = RemoteOperationResult<Boolean>(e)
Timber.e( Timber.e(
e, e,
"Existence check for ${client.userFilesWebDavUri}${WebdavUtils.encodePath(remotePath)} : ${result.logMessage}" "Existence check for $stringUrl : ${result.logMessage}"
) )
result.data = false
result result
} finally {
client.setFollowRedirects(previousFollowRedirects)
} }
} }
/**
* @return 'True' if the operation was executed and at least one redirection was followed.
*/
fun wasRedirected() = redirectionPath?.redirectionsCount ?: 0 > 0
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS
companion object { companion object {

View File

@ -1,129 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account.
*
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author Christian Schabesberger
* @author David González V.
*/
public class CopyRemoteFileOperation extends RemoteOperation<String> {
private static final int COPY_READ_TIMEOUT = 600000;
private static final int COPY_CONNECTION_TIMEOUT = 5000;
private String mSrcRemotePath;
private String mTargetRemotePath;
private boolean mOverwrite;
/**
* Constructor.
* <p/>
* TODO Paths should finish in "/" in the case of folders. ?
*
* @param srcRemotePath Remote path of the file/folder to move.
* @param targetRemotePath Remove path desired for the file/folder after moving it.
*/
public CopyRemoteFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite
) {
mSrcRemotePath = srcRemotePath;
mTargetRemotePath = targetRemotePath;
mOverwrite = overwrite;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<String> run(OwnCloudClient client) {
if (mTargetRemotePath.equals(mSrcRemotePath)) {
// nothing to do!
return new RemoteOperationResult<>(ResultCode.OK);
}
if (mTargetRemotePath.startsWith(mSrcRemotePath)) {
return new RemoteOperationResult<>(ResultCode.INVALID_COPY_INTO_DESCENDANT);
}
/// perform remote operation
RemoteOperationResult result;
try {
CopyMethod copyMethod =
new CopyMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mSrcRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath),
mOverwrite);
copyMethod.setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS);
copyMethod.setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(copyMethod);
if (status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT) {
String fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID);
result = new RemoteOperationResult<>(ResultCode.OK);
result.setData(fileRemoteId);
} else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) {
result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
client.exhaustResponse(copyMethod.getResponseBodyAsStream());
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
} else {
result = new RemoteOperationResult<>(copyMethod);
client.exhaustResponse(copyMethod.getResponseBodyAsStream());
}
Timber.i("Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Copy " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
}
return result;
}
}

View File

@ -0,0 +1,133 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.CopyMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation copying a remote file or folder in the ownCloud server to a different folder
* in the same account.
*
* Allows renaming the copying file/folder at the same time.
*
* @author David A. Velasco
* @author Christian Schabesberger
* @author David González V.
* @author Juan Carlos Garrote Gascón
* @author Manuel Plazas Palacio
*
* @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it.
*/
class CopyRemoteFileOperation(
private val sourceRemotePath: String,
private val targetRemotePath: String,
private val sourceSpaceWebDavUrl: String? = null,
private val targetSpaceWebDavUrl: String? = null,
private val forceOverride: Boolean = false,
) : RemoteOperation<String>() {
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<String> {
if (targetRemotePath == sourceRemotePath && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
// nothing to do!
return RemoteOperationResult(ResultCode.OK)
}
if (targetRemotePath.startsWith(sourceRemotePath) && sourceSpaceWebDavUrl == targetSpaceWebDavUrl) {
return RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT)
}
/// perform remote operation
var result: RemoteOperationResult<String>
try {
val copyMethod = CopyMethod(
url = URL((sourceSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
destinationUrl = (targetSpaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
forceOverride = forceOverride,
).apply {
addRequestHeaders(this)
setReadTimeout(COPY_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(COPY_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(copyMethod)
when {
isSuccess(status) -> {
val fileRemoteId = copyMethod.getResponseHeader(HttpConstants.OC_FILE_REMOTE_ID)
result = RemoteOperationResult(ResultCode.OK)
result.setData(fileRemoteId)
}
isPreconditionFailed(status) -> {
result = RemoteOperationResult(ResultCode.INVALID_OVERWRITE)
client.exhaustResponse(copyMethod.getResponseBodyAsStream())
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
}
else -> {
result = RemoteOperationResult(copyMethod)
client.exhaustResponse(copyMethod.getResponseBodyAsStream())
}
}
Timber.i("Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Copy $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
}
return result
}
private fun addRequestHeaders(copyMethod: CopyMethod) {
//Adding this because the library has an error with override
if (copyMethod.forceOverride) {
copyMethod.setRequestHeader(OVERWRITE, TRUE)
}
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED
companion object {
private const val COPY_READ_TIMEOUT = 10L
private const val COPY_CONNECTION_TIMEOUT = 6L
private const val OVERWRITE = "overwrite"
private const val TRUE = "T"
}
}

View File

@ -1,113 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2019 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation performing the creation of a new folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class CreateRemoteFolderOperation extends RemoteOperation {
private static final int READ_TIMEOUT = 30000;
private static final int CONNECTION_TIMEOUT = 5000;
private String mRemotePath;
private boolean mCreateFullPath;
protected boolean createChunksFolder;
/**
* Constructor
*
* @param remotePath Full path to the new directory to create in the remote server.
* @param createFullPath 'True' means that all the ancestor folders should be created.
*/
public CreateRemoteFolderOperation(String remotePath, boolean createFullPath) {
mRemotePath = remotePath;
mCreateFullPath = createFullPath;
createChunksFolder = false;
}
/**
* Performs the operation
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = createFolder(client);
if (!result.isSuccess() && mCreateFullPath &&
RemoteOperationResult.ResultCode.CONFLICT == result.getCode()) {
result = createParentFolder(FileUtils.getParentPath(mRemotePath), client);
if (result.isSuccess()) {
result = createFolder(client); // second (and last) try
}
}
return result;
}
private RemoteOperationResult createFolder(OwnCloudClient client) {
RemoteOperationResult result;
try {
Uri webDavUri = createChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
final MkColMethod mkcol = new MkColMethod(new URL(webDavUri + WebdavUtils.encodePath(mRemotePath)));
mkcol.setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
mkcol.setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(mkcol);
result = (status == HttpConstants.HTTP_CREATED)
? new RemoteOperationResult<>(ResultCode.OK)
: new RemoteOperationResult<>(mkcol);
Timber.d("Create directory " + mRemotePath + ": " + result.getLogMessage());
client.exhaustResponse(mkcol.getResponseBodyAsStream());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Create directory " + mRemotePath + ": " + result.getLogMessage());
}
return result;
}
private RemoteOperationResult createParentFolder(String parentPath, OwnCloudClient client) {
RemoteOperation operation = new CreateRemoteFolderOperation(parentPath, mCreateFullPath);
return operation.execute(client);
}
}

View File

@ -0,0 +1,110 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MkColMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the creation of a new folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*
* @param remotePath Full path to the new directory to create in the remote server.
* @param createFullPath 'True' means that all the ancestor folders should be created.
*/
class CreateRemoteFolderOperation(
val remotePath: String,
private val createFullPath: Boolean,
private val isChunksFolder: Boolean = false,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result = createFolder(client)
if (!result.isSuccess && createFullPath && result.code == ResultCode.CONFLICT) {
result = createParentFolder(FileUtils.getParentPath(remotePath), client)
if (result.isSuccess) {
// Second and last try
result = createFolder(client)
}
}
return result
}
private fun createFolder(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
val webDavUri = if (isChunksFolder) {
client.uploadsWebDavUri.toString()
} else {
spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
}
val mkCol = MkColMethod(
URL(webDavUri + WebdavUtils.encodePath(remotePath))
).apply {
setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(mkCol)
result =
if (status == HttpConstants.HTTP_CREATED) {
RemoteOperationResult(ResultCode.OK)
} else {
RemoteOperationResult(mkCol)
}
Timber.d("Create directory $remotePath: ${result.logMessage}")
client.exhaustResponse(mkCol.getResponseBodyAsStream())
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Create directory $remotePath: ${result.logMessage}")
}
return result
}
private fun createParentFolder(parentPath: String, client: OwnCloudClient): RemoteOperationResult<Unit> {
val operation: RemoteOperation<Unit> = CreateRemoteFolderOperation(parentPath, createFullPath)
return operation.execute(client)
}
companion object {
private const val READ_TIMEOUT: Long = 30_000
private const val CONNECTION_TIMEOUT: Long = 5_000
}
}

View File

@ -1,219 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Remote operation performing the download of a remote file in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class DownloadRemoteFileOperation extends RemoteOperation {
private static final int FORBIDDEN_ERROR = 403;
private static final int SERVICE_UNAVAILABLE_ERROR = 503;
private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
private long mModificationTimestamp = 0;
private String mEtag = "";
private GetMethod mGet;
private String mRemotePath;
private String mLocalFolderPath;
public DownloadRemoteFileOperation(String remotePath, String localFolderPath) {
mRemotePath = remotePath;
mLocalFolderPath = localFolderPath;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
/// download will be performed to a temporal file, then moved to the final location
File tmpFile = new File(getTmpPath());
/// perform the download
try {
tmpFile.getParentFile().mkdirs();
result = downloadFile(client, tmpFile);
Timber.i("Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Download of " + mRemotePath + " to " + getTmpPath() + ": " + result.getLogMessage());
}
return result;
}
private RemoteOperationResult downloadFile(OwnCloudClient client, File targetFile) throws
Exception {
RemoteOperationResult result;
int status;
boolean savedFile = false;
mGet = new GetMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)));
Iterator<OnDatatransferProgressListener> it;
FileOutputStream fos = null;
BufferedInputStream bis = null;
try {
status = client.executeHttpMethod(mGet);
if (isSuccess(status)) {
targetFile.createNewFile();
bis = new BufferedInputStream(mGet.getResponseBodyAsStream());
fos = new FileOutputStream(targetFile);
long transferred = 0;
String contentLength = mGet.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER);
long totalToTransfer =
(contentLength != null
&& contentLength.length() > 0)
? Long.parseLong(contentLength)
: 0;
byte[] bytes = new byte[4096];
int readResult;
while ((readResult = bis.read(bytes)) != -1) {
synchronized (mCancellationRequested) {
if (mCancellationRequested.get()) {
mGet.abort();
throw new OperationCancelledException();
}
}
fos.write(bytes, 0, readResult);
transferred += readResult;
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readResult, transferred, totalToTransfer,
targetFile.getName());
}
}
}
if (transferred == totalToTransfer) { // Check if the file is completed
savedFile = true;
final String modificationTime =
mGet.getResponseHeaders().get("Last-Modified") != null
? mGet.getResponseHeaders().get("Last-Modified")
: mGet.getResponseHeader("last-modified");
if (modificationTime != null) {
final Date d = WebdavUtils.parseResponseDate(modificationTime);
mModificationTimestamp = (d != null) ? d.getTime() : 0;
} else {
Timber.e("Could not read modification time from response downloading %s", mRemotePath);
}
mEtag = WebdavUtils.getEtagFromResponse(mGet);
// Get rid of extra quotas
mEtag = mEtag.replace("\"", "");
if (mEtag.length() == 0) {
Timber.e("Could not read eTag from response downloading %s", mRemotePath);
}
} else {
client.exhaustResponse(mGet.getResponseBodyAsStream());
// TODO some kind of error control!
}
} else if (status != FORBIDDEN_ERROR && status != SERVICE_UNAVAILABLE_ERROR) {
client.exhaustResponse(mGet.getResponseBodyAsStream());
} // else, body read by RemoteOperationResult constructor
result = isSuccess(status)
? new RemoteOperationResult<>(RemoteOperationResult.ResultCode.OK)
: new RemoteOperationResult<>(mGet);
} finally {
if (fos != null) {
fos.close();
}
if (bis != null) {
bis.close();
}
if (!savedFile && targetFile.exists()) {
targetFile.delete();
}
}
return result;
}
private boolean isSuccess(int status) {
return (status == HttpConstants.HTTP_OK);
}
private String getTmpPath() {
return mLocalFolderPath + mRemotePath;
}
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
public void cancel() {
mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it
}
public long getModificationTimestamp() {
return mModificationTimestamp;
}
public String getEtag() {
return mEtag;
}
}

View File

@ -0,0 +1,185 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean
/**
* Remote operation performing the download of a remote file in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
class DownloadRemoteFileOperation(
private val remotePath: String,
localFolderPath: String,
private val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
private val cancellationRequested = AtomicBoolean(false)
private val dataTransferListeners: MutableSet<OnDatatransferProgressListener> = HashSet()
var modificationTimestamp: Long = 0
private set
var etag: String = ""
private set
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
// download will be performed to a temporal file, then moved to the final location
val tmpFile = File(tmpPath)
// perform the download
return try {
tmpFile.parentFile?.mkdirs()
downloadFile(client, tmpFile).also { result ->
Timber.i("Download of $remotePath to $tmpPath: ${result.logMessage}")
}
} catch (e: Exception) {
RemoteOperationResult<Unit>(e).also { result ->
Timber.e(e, "Download of $remotePath to $tmpPath: ${result.logMessage}")
}
}
}
@Throws(Exception::class)
private fun downloadFile(client: OwnCloudClient, targetFile: File): RemoteOperationResult<Unit> {
val result: RemoteOperationResult<Unit>
var it: Iterator<OnDatatransferProgressListener>
var fos: FileOutputStream? = null
var bis: BufferedInputStream? = null
var savedFile = false
val webDavUri = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
val getMethod = GetMethod(URL(webDavUri + WebdavUtils.encodePath(remotePath)))
try {
val status = client.executeHttpMethod(getMethod)
if (isSuccess(status)) {
targetFile.createNewFile()
bis = BufferedInputStream(getMethod.getResponseBodyAsStream())
fos = FileOutputStream(targetFile)
var transferred: Long = 0
val contentLength = getMethod.getResponseHeader(HttpConstants.CONTENT_LENGTH_HEADER)
val totalToTransfer = if (!contentLength.isNullOrEmpty()) {
contentLength.toLong()
} else {
0
}
val bytes = ByteArray(4096)
var readResult: Int
while (bis.read(bytes).also { readResult = it } != -1) {
synchronized(cancellationRequested) {
if (cancellationRequested.get()) {
getMethod.abort()
throw OperationCancelledException()
}
}
fos.write(bytes, 0, readResult)
transferred += readResult.toLong()
synchronized(dataTransferListeners) {
it = dataTransferListeners.iterator()
while (it.hasNext()) {
it.next()
.onTransferProgress(readResult.toLong(), transferred, totalToTransfer, targetFile.name)
}
}
}
if (transferred == totalToTransfer) { // Check if the file is completed
savedFile = true
val modificationTime =
getMethod.getResponseHeaders()?.get("Last-Modified")
?: getMethod.getResponseHeader("last-modified")
if (modificationTime != null) {
val modificationDate = WebdavUtils.parseResponseDate(modificationTime)
modificationTimestamp = modificationDate?.time ?: 0
} else {
Timber.e("Could not read modification time from response downloading %s", remotePath)
}
etag = WebdavUtils.getEtagFromResponse(getMethod)
// Get rid of extra quotas
etag = etag.replace("\"", "")
if (etag.isEmpty()) {
Timber.e("Could not read eTag from response downloading %s", remotePath)
}
} else {
Timber.e("Content-Length not equal to transferred bytes.")
Timber.d("totalToTransfer = $totalToTransfer, transferred = $transferred")
client.exhaustResponse(getMethod.getResponseBodyAsStream())
// TODO some kind of error control!
}
} else if (status != HttpConstants.HTTP_FORBIDDEN && status != HttpConstants.HTTP_SERVICE_UNAVAILABLE) {
client.exhaustResponse(getMethod.getResponseBodyAsStream())
} // else, body read by RemoteOperationResult constructor
result =
if (isSuccess(status)) {
RemoteOperationResult(RemoteOperationResult.ResultCode.OK)
} else {
RemoteOperationResult(getMethod)
}
} finally {
fos?.close()
bis?.close()
if (!savedFile && targetFile.exists()) {
targetFile.delete()
}
}
return result
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
private val tmpPath: String = localFolderPath + remotePath
fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListeners) { dataTransferListeners.add(listener) }
}
fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener?) {
synchronized(dataTransferListeners) { dataTransferListeners.remove(listener) }
}
fun cancel() {
cancellationRequested.set(true) // atomic set; there is no need of synchronizing it
}
}

View File

@ -32,6 +32,7 @@ public class FileUtils {
public static final String FINAL_CHUNKS_FILE = ".file"; public static final String FINAL_CHUNKS_FILE = ".file";
public static final String MIME_DIR = "DIR"; public static final String MIME_DIR = "DIR";
public static final String MIME_DIR_UNIX = "httpd/unix-directory"; public static final String MIME_DIR_UNIX = "httpd/unix-directory";
public static final String MODE_READ_ONLY = "r";
static String getParentPath(String remotePath) { static String getParentPath(String remotePath) {
String parentPath = new File(remotePath).getParent(); String parentPath = new File(remotePath).getParent();

View File

@ -0,0 +1,77 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Operation to get the base url, which might differ in case of a redirect.
*
* @author Christian Schabesberger
*/
class GetBaseUrlRemoteOperation : RemoteOperation<String?>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<String?> {
return try {
val stringUrl = client.baseFilesWebDavUri.toString()
val propFindMethod = PropfindMethod(URL(stringUrl), 0, DavUtils.allPropSet).apply {
setReadTimeout(TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(propFindMethod)
if (isSuccess(status)) {
RemoteOperationResult<String?>(RemoteOperationResult.ResultCode.OK).apply {
data = propFindMethod.getFinalUrl().toString()
}
} else {
RemoteOperationResult<String?>(propFindMethod).apply {
data = null
}
}
} catch (e: Exception) {
Timber.e(e, "Could not get actuall (or redirected) base URL from base url (/).")
RemoteOperationResult<String?>(e)
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_MULTI_STATUS
companion object {
/**
* Maximum time to wait for a response from the server in milliseconds.
*/
private const val TIMEOUT = 10_000L
}
}

View File

@ -1,146 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account.
* <p>
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author David González Verdugo
*/
public class MoveRemoteFileOperation extends RemoteOperation {
private static final int MOVE_READ_TIMEOUT = 600000;
private static final int MOVE_CONNECTION_TIMEOUT = 5000;
private String mSrcRemotePath;
private String mTargetRemotePath;
private boolean mOverwrite;
protected boolean moveChunkedFile = false;
protected String mFileLastModifTimestamp;
protected long mFileLength;
/**
* Constructor.
* <p>
* TODO Paths should finish in "/" in the case of folders. ?
*
* @param srcRemotePath Remote path of the file/folder to move.
* @param targetRemotePath Remote path desired for the file/folder after moving it.
*/
public MoveRemoteFileOperation(String srcRemotePath,
String targetRemotePath,
boolean overwrite) {
mSrcRemotePath = srcRemotePath;
mTargetRemotePath = targetRemotePath;
mOverwrite = overwrite;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
if (mTargetRemotePath.equals(mSrcRemotePath)) {
// nothing to do!
return new RemoteOperationResult<>(ResultCode.OK);
}
if (mTargetRemotePath.startsWith(mSrcRemotePath)) {
return new RemoteOperationResult<>(ResultCode.INVALID_MOVE_INTO_DESCENDANT);
}
/// perform remote operation
RemoteOperationResult result;
try {
// After finishing a chunked upload, we have to move the resulting file from uploads folder to files one,
// so this uri has to be customizable
Uri srcWebDavUri = moveChunkedFile ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
final MoveMethod move = new MoveMethod(
new URL(srcWebDavUri + WebdavUtils.encodePath(mSrcRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mTargetRemotePath),
mOverwrite);
if (moveChunkedFile) {
move.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp);
move.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(mFileLength));
}
move.setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS);
move.setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(move);
/// process response
if (isSuccess(status)) {
result = new RemoteOperationResult<>(ResultCode.OK);
} else if (status == HttpConstants.HTTP_PRECONDITION_FAILED && !mOverwrite) {
result = new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
client.exhaustResponse(move.getResponseBodyAsStream());
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
} else {
result = new RemoteOperationResult<>(move);
client.exhaustResponse(move.getResponseBodyAsStream());
}
Timber.i("Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Move " + mSrcRemotePath + " to " + mTargetRemotePath + ": " + result.getLogMessage());
}
return result;
}
protected boolean isSuccess(int status) {
return status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT;
}
}

View File

@ -0,0 +1,149 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation moving a remote file or folder in the ownCloud server to a different folder
* in the same account and space.
*
* Allows renaming the moving file/folder at the same time.
*
* @author David A. Velasco
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
* @author Manuel Plazas Palacio
*
* @param sourceRemotePath Remote path of the file/folder to copy.
* @param targetRemotePath Remote path desired for the file/folder to copy it.
*/
open class MoveRemoteFileOperation(
private val sourceRemotePath: String,
private val targetRemotePath: String,
private val spaceWebDavUrl: String? = null,
private val forceOverride: Boolean = false,
) : RemoteOperation<Unit>() {
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
if (targetRemotePath == sourceRemotePath) {
// nothing to do!
return RemoteOperationResult(ResultCode.OK)
}
if (targetRemotePath.startsWith(sourceRemotePath)) {
return RemoteOperationResult(ResultCode.INVALID_MOVE_INTO_DESCENDANT)
}
/// perform remote operation
var result: RemoteOperationResult<Unit>
try {
// After finishing a chunked upload, we have to move the resulting file from uploads folder to files one,
// so this uri has to be customizable
val srcWebDavUri = getSrcWebDavUriForClient(client)
val moveMethod = MoveMethod(
url = URL((spaceWebDavUrl ?: srcWebDavUri.toString()) + WebdavUtils.encodePath(sourceRemotePath)),
destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(targetRemotePath),
forceOverride = forceOverride,
).apply {
addRequestHeaders(this)
setReadTimeout(MOVE_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(MOVE_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(moveMethod)
when {
isSuccess(status) -> {
result = RemoteOperationResult<Unit>(ResultCode.OK)
}
isPreconditionFailed(status) -> {
result = RemoteOperationResult<Unit>(ResultCode.INVALID_OVERWRITE)
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
/// for other errors that could be explicitly handled, check first:
/// http://www.webdav.org/specs/rfc4918.html#rfc.section.9.9.4
}
else -> {
result = RemoteOperationResult<Unit>(moveMethod)
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
}
}
Timber.i("Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult<Unit>(e)
Timber.e(e, "Move $sourceRemotePath to $targetRemotePath: ${result.logMessage}")
}
return result
}
/**
* For standard moves, we will use [OwnCloudClient.getUserFilesWebDavUri].
* In case we need a different source Uri, override this method.
*/
open fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.userFilesWebDavUri
/**
* For standard moves, we won't need any special headers.
* In case new headers are needed, override this method
*/
open fun addRequestHeaders(moveMethod: MoveMethod) {
//Adding this because the library has an error with override
if (moveMethod.forceOverride) {
moveMethod.setRequestHeader(OVERWRITE, TRUE)
}
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
private fun isPreconditionFailed(status: Int) = status == HttpConstants.HTTP_PRECONDITION_FAILED
companion object {
private const val MOVE_READ_TIMEOUT = 10L
private const val MOVE_CONNECTION_TIMEOUT = 6L
private const val OVERWRITE = "overwrite"
private const val TRUE = "T"
}
}

View File

@ -1,107 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils;
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import static com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the read a file from the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class ReadRemoteFileOperation extends RemoteOperation<RemoteFile> {
private static final int SYNC_READ_TIMEOUT = 40000;
private static final int SYNC_CONNECTION_TIMEOUT = 5000;
private String mRemotePath;
/**
* Constructor
*
* @param remotePath Remote path of the file.
*/
public ReadRemoteFileOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<RemoteFile> run(OwnCloudClient client) {
PropfindMethod propfind;
RemoteOperationResult<RemoteFile> result;
/// take the duty of check the server for the current state of the file there
try {
// remote request
propfind = new PropfindMethod(new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)),
DEPTH_0,
DavUtils.getAllPropset());
propfind.setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS);
propfind.setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(propfind);
if (status == HttpConstants.HTTP_MULTI_STATUS
|| status == HttpConstants.HTTP_OK) {
final RemoteFile file = new RemoteFile(propfind.getRoot(), AccountUtils.getUserId(mAccount, mContext));
result = new RemoteOperationResult<>(OK);
result.setData(file);
} else {
result = new RemoteOperationResult<>(propfind);
client.exhaustResponse(propfind.getResponseBodyAsStream());
}
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Synchronizing file %s", mRemotePath);
}
return result;
}
}

View File

@ -0,0 +1,107 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2016 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants.DEPTH_0
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the read a file from the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
class ReadRemoteFileOperation(
val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<RemoteFile>() {
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteFile> {
try {
val propFind = PropfindMethod(
url = getFinalWebDavUrl(),
depth = DEPTH_0,
propertiesToRequest = DavUtils.allPropSet
).apply {
setReadTimeout(SYNC_READ_TIMEOUT, TimeUnit.SECONDS)
setConnectionTimeout(SYNC_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
}
val status = client.executeHttpMethod(propFind)
Timber.i("Read remote file $remotePath with status ${propFind.statusCode}")
return if (isSuccess(status)) {
val remoteFile = RemoteFile.getRemoteFileFromDav(
davResource = propFind.root!!,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
RemoteOperationResult<RemoteFile>(RemoteOperationResult.ResultCode.OK).apply {
data = remoteFile
}
} else {
RemoteOperationResult<RemoteFile>(propFind).also {
client.exhaustResponse(propFind.getResponseBodyAsStream())
}
}
} catch (exception: Exception) {
return RemoteOperationResult(exception)
}
}
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int) = status.isOneOf(HTTP_MULTI_STATUS, HTTP_OK)
companion object {
private const val SYNC_READ_TIMEOUT = 40_000L
private const val SYNC_CONNECTION_TIMEOUT = 5_000L
}
}

View File

@ -1,127 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import at.bitfire.dav4jvm.Response;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants;
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils;
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import java.util.ArrayList;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the read of remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class ReadRemoteFolderOperation extends RemoteOperation<ArrayList<RemoteFile>> {
private String mRemotePath;
/**
* Constructor
*
* @param remotePath Remote path of the file.
*/
public ReadRemoteFolderOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult<ArrayList<RemoteFile>> run(OwnCloudClient client) {
RemoteOperationResult<ArrayList<RemoteFile>> result = null;
try {
PropfindMethod propfindMethod = new PropfindMethod(
new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)),
DavConstants.DEPTH_1,
DavUtils.getAllPropset());
client.setFollowRedirects(true);
int status = client.executeHttpMethod(propfindMethod);
if (isSuccess(status)) {
ArrayList<RemoteFile> mFolderAndFiles = new ArrayList<>();
// parse data from remote folder
mFolderAndFiles.add(
new RemoteFile(propfindMethod.getRoot(), AccountUtils.getUserId(mAccount, mContext))
);
// loop to update every child
for (Response resource : propfindMethod.getMembers()) {
RemoteFile file = new RemoteFile(resource, AccountUtils.getUserId(mAccount, mContext));
mFolderAndFiles.add(file);
}
// Result of the operation
result = new RemoteOperationResult<>(OK);
result.setData(mFolderAndFiles);
} else { // synchronization failed
result = new RemoteOperationResult<>(propfindMethod);
}
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
} finally {
if (result == null) {
Timber.e("Synchronized " + mRemotePath + ": result is null");
} else if (result.isSuccess()) {
Timber.i("Synchronized " + mRemotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
Timber.e(result.getException(), "Synchronized " + mRemotePath + ": " + result.getLogMessage());
} else {
Timber.e("Synchronized " + mRemotePath + ": " + result.getLogMessage());
}
}
}
return result;
}
private boolean isSuccess(int status) {
return status == HttpConstants.HTTP_MULTI_STATUS || status == HttpConstants.HTTP_OK;
}
}

View File

@ -0,0 +1,118 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import at.bitfire.dav4jvm.PropertyRegistry
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_MULTI_STATUS
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.webdav.DavConstants
import com.owncloud.android.lib.common.http.methods.webdav.DavUtils
import com.owncloud.android.lib.common.http.methods.webdav.PropfindMethod
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
/**
* Remote operation performing the read of remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
class ReadRemoteFolderOperation(
val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<ArrayList<RemoteFile>>() {
/**
* Performs the read operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
override fun run(client: OwnCloudClient): RemoteOperationResult<ArrayList<RemoteFile>> {
try {
PropertyRegistry.register(OCShareTypes.Factory())
val propfindMethod = PropfindMethod(
getFinalWebDavUrl(),
DavConstants.DEPTH_1,
DavUtils.allPropSet
)
val status = client.executeHttpMethod(propfindMethod)
if (isSuccess(status)) {
val mFolderAndFiles = ArrayList<RemoteFile>()
val remoteFolder = RemoteFile.getRemoteFileFromDav(
davResource = propfindMethod.root!!,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
mFolderAndFiles.add(remoteFolder)
// loop to update every child
propfindMethod.members.forEach { resource ->
val remoteFile = RemoteFile.getRemoteFileFromDav(
davResource = resource,
userId = AccountUtils.getUserId(mAccount, mContext),
userName = mAccount.name,
spaceWebDavUrl = spaceWebDavUrl,
)
mFolderAndFiles.add(remoteFile)
}
// Result of the operation
return RemoteOperationResult<ArrayList<RemoteFile>>(ResultCode.OK).apply {
data = mFolderAndFiles
Timber.i("Synchronized $remotePath with ${mFolderAndFiles.size} files. ${this.logMessage}")
}
} else { // synchronization failed
return RemoteOperationResult<ArrayList<RemoteFile>>(propfindMethod).also {
Timber.w("Synchronized $remotePath ${it.logMessage}")
}
}
} catch (e: Exception) {
return RemoteOperationResult<ArrayList<RemoteFile>>(e).also {
Timber.e(it.exception, "Synchronized $remotePath")
}
}
}
private fun getFinalWebDavUrl(): URL {
val baseWebDavUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
return URL(baseWebDavUrl + WebdavUtils.encodePath(remotePath))
}
private fun isSuccess(status: Int): Boolean = status.isOneOf(HTTP_OK, HTTP_MULTI_STATUS)
}

View File

@ -1,322 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.os.Parcel;
import android.os.Parcelable;
import at.bitfire.dav4jvm.Property;
import at.bitfire.dav4jvm.Response;
import at.bitfire.dav4jvm.property.CreationDate;
import at.bitfire.dav4jvm.property.GetContentLength;
import at.bitfire.dav4jvm.property.GetContentType;
import at.bitfire.dav4jvm.property.GetETag;
import at.bitfire.dav4jvm.property.GetLastModified;
import at.bitfire.dav4jvm.property.OCId;
import at.bitfire.dav4jvm.property.OCPermissions;
import at.bitfire.dav4jvm.property.OCPrivatelink;
import at.bitfire.dav4jvm.property.OCSize;
import at.bitfire.dav4jvm.property.QuotaAvailableBytes;
import at.bitfire.dav4jvm.property.QuotaUsedBytes;
import java.io.File;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* Contains the data of a Remote File from a WebDavEntry
*
* @author masensio
* @author Christian Schabesberger
*/
public class RemoteFile implements Parcelable, Serializable {
/**
* Parcelable Methods
*/
public static final Parcelable.Creator<RemoteFile> CREATOR = new Parcelable.Creator<RemoteFile>() {
@Override
public RemoteFile createFromParcel(Parcel source) {
return new RemoteFile(source);
}
@Override
public RemoteFile[] newArray(int size) {
return new RemoteFile[size];
}
};
/**
* Generated - should be refreshed every time the class changes!!
*/
private static final long serialVersionUID = -8965995357413958539L;
private String mRemotePath;
private String mMimeType;
private long mLength;
private long mCreationTimestamp;
private long mModifiedTimestamp;
private String mEtag;
private String mPermissions;
private String mRemoteId;
private long mSize;
private BigDecimal mQuotaUsedBytes;
private BigDecimal mQuotaAvailableBytes;
private String mPrivateLink;
public RemoteFile() {
resetData();
}
/**
* Create new {@link RemoteFile} with given path.
* <p>
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first
* character in 'path'.
*
* @param path The remote path of the file.
*/
public RemoteFile(String path) {
resetData();
if (path == null || path.length() <= 0 || !path.startsWith(File.separator)) {
throw new IllegalArgumentException("Trying to create a OCFile with a non valid remote path: " + path);
}
mRemotePath = path;
mCreationTimestamp = 0;
mLength = 0;
mMimeType = FileUtils.MIME_DIR;
mQuotaUsedBytes = BigDecimal.ZERO;
mQuotaAvailableBytes = BigDecimal.ZERO;
mPrivateLink = null;
}
public RemoteFile(final Response davResource, String userId) {
this(RemoteFileUtil.Companion.getRemotePathFromUrl(davResource.getHref(), userId));
final List<Property> properties = davResource.getProperties();
for (Property property : properties) {
if (property instanceof CreationDate) {
this.setCreationTimestamp(
Long.parseLong(((CreationDate) property).getCreationDate()));
}
if (property instanceof GetContentLength) {
this.setLength(((GetContentLength) property).getContentLength());
}
if (property instanceof GetContentType) {
this.setMimeType(((GetContentType) property).getType());
}
if (property instanceof GetLastModified) {
this.setModifiedTimestamp(((GetLastModified) property).getLastModified());
}
if (property instanceof GetETag) {
this.setEtag(((GetETag) property).getETag());
}
if (property instanceof OCPermissions) {
this.setPermissions(((OCPermissions) property).getPermission());
}
if (property instanceof OCId) {
this.setRemoteId(((OCId) property).getId());
}
if (property instanceof OCSize) {
this.setSize(((OCSize) property).getSize());
}
if (property instanceof QuotaUsedBytes) {
this.setQuotaUsedBytes(
BigDecimal.valueOf(((QuotaUsedBytes) property).getQuotaUsedBytes()));
}
if (property instanceof QuotaAvailableBytes) {
this.setQuotaAvailableBytes(
BigDecimal.valueOf(((QuotaAvailableBytes) property).getQuotaAvailableBytes()));
}
if (property instanceof OCPrivatelink) {
this.setPrivateLink(((OCPrivatelink) property).getLink());
}
}
}
/**
* Reconstruct from parcel
*
* @param source The source parcel
*/
protected RemoteFile(Parcel source) {
readFromParcel(source);
}
/**
* Use this to find out if this file is a folder.
*
* @return true if it is a folder
*/
public boolean isFolder() {
return mMimeType != null && (mMimeType.equals(FileUtils.MIME_DIR) || mMimeType.equals(FileUtils.MIME_DIR_UNIX));
}
/**
* Getters and Setters
*/
public String getRemotePath() {
return mRemotePath;
}
public void setRemotePath(String remotePath) {
this.mRemotePath = remotePath;
}
public String getMimeType() {
return mMimeType;
}
public void setMimeType(String mimeType) {
this.mMimeType = mimeType;
}
public long getLength() {
return mLength;
}
public void setLength(long length) {
this.mLength = length;
}
public long getCreationTimestamp() {
return mCreationTimestamp;
}
public void setCreationTimestamp(long creationTimestamp) {
this.mCreationTimestamp = creationTimestamp;
}
public long getModifiedTimestamp() {
return mModifiedTimestamp;
}
public void setModifiedTimestamp(long modifiedTimestamp) {
this.mModifiedTimestamp = modifiedTimestamp;
}
public String getEtag() {
return mEtag;
}
public void setEtag(String etag) {
this.mEtag = etag;
}
public String getPermissions() {
return mPermissions;
}
public void setPermissions(String permissions) {
this.mPermissions = permissions;
}
public String getRemoteId() {
return mRemoteId;
}
public void setRemoteId(String remoteId) {
this.mRemoteId = remoteId;
}
public long getSize() {
return mSize;
}
public void setSize(long size) {
mSize = size;
}
public void setQuotaUsedBytes(BigDecimal quotaUsedBytes) {
mQuotaUsedBytes = quotaUsedBytes;
}
public void setQuotaAvailableBytes(BigDecimal quotaAvailableBytes) {
mQuotaAvailableBytes = quotaAvailableBytes;
}
public String getPrivateLink() {
return mPrivateLink;
}
public void setPrivateLink(String privateLink) {
mPrivateLink = privateLink;
}
/**
* Used internally. Reset all file properties
*/
private void resetData() {
mRemotePath = null;
mMimeType = null;
mLength = 0;
mCreationTimestamp = 0;
mModifiedTimestamp = 0;
mEtag = null;
mPermissions = null;
mRemoteId = null;
mSize = 0;
mQuotaUsedBytes = null;
mQuotaAvailableBytes = null;
mPrivateLink = null;
}
public void readFromParcel(Parcel source) {
mRemotePath = source.readString();
mMimeType = source.readString();
mLength = source.readLong();
mCreationTimestamp = source.readLong();
mModifiedTimestamp = source.readLong();
mEtag = source.readString();
mPermissions = source.readString();
mRemoteId = source.readString();
mSize = source.readLong();
mQuotaUsedBytes = (BigDecimal) source.readSerializable();
mQuotaAvailableBytes = (BigDecimal) source.readSerializable();
mPrivateLink = source.readString();
}
@Override
public int describeContents() {
return this.hashCode();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mRemotePath);
dest.writeString(mMimeType);
dest.writeLong(mLength);
dest.writeLong(mCreationTimestamp);
dest.writeLong(mModifiedTimestamp);
dest.writeString(mEtag);
dest.writeString(mPermissions);
dest.writeString(mRemoteId);
dest.writeLong(mSize);
dest.writeSerializable(mQuotaUsedBytes);
dest.writeSerializable(mQuotaAvailableBytes);
dest.writeString(mPrivateLink);
}
}

View File

@ -0,0 +1,194 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import android.net.Uri
import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import at.bitfire.dav4jvm.PropStat
import at.bitfire.dav4jvm.Property
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.property.CreationDate
import at.bitfire.dav4jvm.property.GetContentLength
import at.bitfire.dav4jvm.property.GetContentType
import at.bitfire.dav4jvm.property.GetETag
import at.bitfire.dav4jvm.property.GetLastModified
import at.bitfire.dav4jvm.property.OCId
import at.bitfire.dav4jvm.property.OCPermissions
import at.bitfire.dav4jvm.property.OCPrivatelink
import at.bitfire.dav4jvm.property.OCSize
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.properties.OCShareTypes
import com.owncloud.android.lib.common.utils.isOneOf
import com.owncloud.android.lib.resources.shares.ShareType
import com.owncloud.android.lib.resources.shares.ShareType.Companion.fromValue
import kotlinx.parcelize.Parcelize
import okhttp3.HttpUrl
import timber.log.Timber
import java.io.File
/**
* Contains the data of a Remote File from a WebDavEntry
*
* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first character in 'path'.
*
* @author masensio
* @author Christian Schabesberger
* @author Abel García de Prada
*/
@Parcelize
data class RemoteFile(
var remotePath: String,
var mimeType: String = "DIR",
var length: Long = 0,
var creationTimestamp: Long = 0,
var modifiedTimestamp: Long = 0,
var etag: String? = null,
var permissions: String? = null,
var remoteId: String? = null,
var size: Long = 0,
var privateLink: String? = null,
var owner: String,
var sharedByLink: Boolean = false,
var sharedWithSharee: Boolean = false,
) : Parcelable {
// TODO: Quotas not used. Use or remove them.
init {
require(
!(remotePath.isEmpty() || !remotePath.startsWith(File.separator))
) { "Trying to create a OCFile with a non valid remote path: $remotePath" }
}
/**
* Use this to find out if this file is a folder.
*
* @return true if it is a folder
*/
val isFolder
get() = mimeType.isOneOf(MIME_DIR, MIME_DIR_UNIX)
companion object {
const val MIME_DIR = "DIR"
const val MIME_DIR_UNIX = "httpd/unix-directory"
fun getRemoteFileFromDav(
davResource: Response,
userId: String,
userName: String,
spaceWebDavUrl: String? = null
): RemoteFile {
val remotePath = getRemotePathFromUrl(davResource.href, userId, spaceWebDavUrl)
val remoteFile = RemoteFile(remotePath = remotePath, owner = userName)
val properties = getPropertiesEvenIfPostProcessing(davResource)
for (property in properties) {
when (property) {
is CreationDate -> {
remoteFile.creationTimestamp = property.creationDate.toLong()
}
is GetContentLength -> {
remoteFile.length = property.contentLength
}
is GetContentType -> {
property.type?.let { remoteFile.mimeType = it }
}
is GetLastModified -> {
remoteFile.modifiedTimestamp = property.lastModified
}
is GetETag -> {
remoteFile.etag = property.eTag
}
is OCPermissions -> {
remoteFile.permissions = property.permission
}
is OCId -> {
remoteFile.remoteId = property.id
}
is OCSize -> {
remoteFile.size = property.size
}
is OCPrivatelink -> {
remoteFile.privateLink = property.link
}
is OCShareTypes -> {
val list = property.shareTypes
for (i in list.indices) {
val shareType = fromValue(list[i].toInt())
if (shareType == null) {
Timber.d("Illegal share type value: " + list[i])
continue
}
if (shareType == ShareType.PUBLIC_LINK) {
remoteFile.sharedByLink = true
} else if (shareType == ShareType.USER || shareType == ShareType.FEDERATED || shareType == ShareType.GROUP) {
remoteFile.sharedWithSharee = true
}
}
}
}
}
return remoteFile
}
/**
* Retrieves a relative path from a remote file url
*
*
* Example legacy:
* /remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt
*
* Example spaces:
* /dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f38$05bca744-d89f-4e9c-a990-25a0d7f03fe9/Documents/text.txt => /Documents/text.txt
*
* @param url remote file url
* @param userId file owner
* @param spaceWebDavUrl custom web dav url for space
* @return remote relative path of the file
*/
@VisibleForTesting
fun getRemotePathFromUrl(
url: HttpUrl,
userId: String,
spaceWebDavUrl: String? = null,
): String {
val davFilesPath = spaceWebDavUrl ?: (OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId)
val absoluteDavPath = if (spaceWebDavUrl != null) Uri.decode(url.toString()) else Uri.decode(url.encodedPath)
val pathToOc = absoluteDavPath.split(davFilesPath).first()
return absoluteDavPath.replace(pathToOc + davFilesPath, "")
}
private fun getPropertiesEvenIfPostProcessing(response: Response): List<Property> {
return if (response.isSuccess())
response.propstat.filter { propStat -> propStat.isSuccessOrPostProcessing() }.map { it.properties }.flatten()
else
emptyList()
}
private fun PropStat.isSuccessOrPostProcessing() = (status.code / 100 == 2 || status.code == HttpConstants.HTTP_TOO_EARLY)
}
}

View File

@ -1,96 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the removal of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class RemoveRemoteFileOperation extends RemoteOperation {
private String mRemotePath;
protected boolean removeChunksFolder = false;
/**
* Constructor
*
* @param remotePath RemotePath of the remote file or folder to remove from the server
*/
public RemoveRemoteFileOperation(String remotePath) {
mRemotePath = remotePath;
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
try {
Uri srcWebDavUri = removeChunksFolder ? client.getUploadsWebDavUri() : client.getUserFilesWebDavUri();
DeleteMethod deleteMethod = new DeleteMethod(
new URL(srcWebDavUri + WebdavUtils.encodePath(mRemotePath)));
int status = client.executeHttpMethod(deleteMethod);
result = isSuccess(status) ?
new RemoteOperationResult<>(OK) :
new RemoteOperationResult<>(deleteMethod);
Timber.i("Remove " + mRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Remove " + mRemotePath + ": " + result.getLogMessage());
}
return result;
}
private boolean isSuccess(int status) {
return status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_NO_CONTENT;
}
}

View File

@ -0,0 +1,81 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_NO_CONTENT
import com.owncloud.android.lib.common.http.HttpConstants.HTTP_OK
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
/**
* Remote operation performing the removal of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
*/
open class RemoveRemoteFileOperation(
private val remotePath: String,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
val srcWebDavUri = getSrcWebDavUriForClient(client)
val deleteMethod = DeleteMethod(
URL(srcWebDavUri + WebdavUtils.encodePath(remotePath))
)
val status = client.executeHttpMethod(deleteMethod)
result = if (isSuccess(status)) {
RemoteOperationResult<Unit>(ResultCode.OK)
} else {
RemoteOperationResult<Unit>(deleteMethod)
}
Timber.i("Remove $remotePath: ${result.logMessage}")
} catch (e: Exception) {
result = RemoteOperationResult<Unit>(e)
Timber.e(e, "Remove $remotePath: ${result.logMessage}")
}
return result
}
/**
* For standard removals, we will use [OwnCloudClient.getUserFilesWebDavUri].
* In case we need a different source Uri, override this method.
*/
open fun getSrcWebDavUriForClient(client: OwnCloudClient): String = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
private fun isSuccess(status: Int) = status.isOneOf(HTTP_OK, HTTP_NO_CONTENT)
}

View File

@ -1,130 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2019 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import timber.log.Timber;
import java.io.File;
import java.net.URL;
import java.util.concurrent.TimeUnit;
/**
* Remote operation performing the rename of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
public class RenameRemoteFileOperation extends RemoteOperation {
private static final int RENAME_READ_TIMEOUT = 600000;
private static final int RENAME_CONNECTION_TIMEOUT = 5000;
private String mOldName;
private String mOldRemotePath;
private String mNewName;
private String mNewRemotePath;
/**
* Constructor
*
* @param oldName Old name of the file.
* @param oldRemotePath Old remote path of the file.
* @param newName New name to set as the name of file.
* @param isFolder 'true' for folder and 'false' for files
*/
public RenameRemoteFileOperation(String oldName, String oldRemotePath, String newName,
boolean isFolder) {
mOldName = oldName;
mOldRemotePath = oldRemotePath;
mNewName = newName;
String parent = (new File(mOldRemotePath)).getParent();
parent = (parent.endsWith(File.separator)) ? parent : parent + File.separator;
mNewRemotePath = parent + mNewName;
if (isFolder) {
mNewRemotePath += File.separator;
}
}
/**
* Performs the rename operation.
*
* @param client Client object to communicate with the remote ownCloud server.
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
try {
if (mNewName.equals(mOldName)) {
return new RemoteOperationResult<>(ResultCode.OK);
}
if (targetPathIsUsed(client)) {
return new RemoteOperationResult<>(ResultCode.INVALID_OVERWRITE);
}
final MoveMethod move = new MoveMethod(new URL(client.getUserFilesWebDavUri() +
WebdavUtils.encodePath(mOldRemotePath)),
client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mNewRemotePath), false);
move.setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS);
move.setConnectionTimeout(RENAME_READ_TIMEOUT, TimeUnit.SECONDS);
final int status = client.executeHttpMethod(move);
final RemoteOperationResult result =
(status == HttpConstants.HTTP_CREATED || status == HttpConstants.HTTP_NO_CONTENT)
? new RemoteOperationResult<>(ResultCode.OK)
: new RemoteOperationResult<>(move);
Timber.i("Rename " + mOldRemotePath + " to " + mNewRemotePath + ": " + result.getLogMessage());
client.exhaustResponse(move.getResponseBodyAsStream());
return result;
} catch (Exception e) {
final RemoteOperationResult result = new RemoteOperationResult<>(e);
Timber.e(e,
"Rename " + mOldRemotePath + " to " + ((mNewRemotePath == null) ? mNewName : mNewRemotePath) + ":" +
" " + result.getLogMessage());
return result;
}
}
/**
* Checks if a file with the new name already exists.
*
* @return 'True' if the target path is already used by an existing file.
*/
private boolean targetPathIsUsed(OwnCloudClient client) {
CheckPathExistenceRemoteOperation checkPathExistenceRemoteOperation =
new CheckPathExistenceRemoteOperation(mNewRemotePath, false);
RemoteOperationResult exists = checkPathExistenceRemoteOperation.execute(client);
return exists.isSuccess();
}
}

View File

@ -0,0 +1,120 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.io.File
import java.net.URL
import java.util.concurrent.TimeUnit
/**
* Remote operation performing the rename of a remote file or folder in the ownCloud server.
*
* @author David A. Velasco
* @author masensio
*/
class RenameRemoteFileOperation(
private val oldName: String,
private val oldRemotePath: String,
private val newName: String,
isFolder: Boolean,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
private var newRemotePath: String
init {
var parent = (File(oldRemotePath)).parent ?: throw IllegalArgumentException()
if (!parent.endsWith(File.separator)) {
parent = parent.plus(File.separator)
}
newRemotePath = parent.plus(newName)
if (isFolder) {
newRemotePath.plus(File.separator)
}
}
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
if (newName == oldName) {
return RemoteOperationResult<Unit>(ResultCode.OK)
}
if (targetPathIsUsed(client)) {
return RemoteOperationResult<Unit>(ResultCode.INVALID_OVERWRITE)
}
val moveMethod: MoveMethod = MoveMethod(
url = URL((spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(oldRemotePath)),
destinationUrl = (spaceWebDavUrl ?: client.userFilesWebDavUri.toString()) + WebdavUtils.encodePath(newRemotePath),
).apply {
setReadTimeout(RENAME_READ_TIMEOUT, TimeUnit.MILLISECONDS)
setConnectionTimeout(RENAME_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
}
val status = client.executeHttpMethod(moveMethod)
result = if (isSuccess(status)) {
RemoteOperationResult<Unit>(ResultCode.OK)
} else {
RemoteOperationResult<Unit>(moveMethod)
}
Timber.i("Rename $oldRemotePath to $newRemotePath: ${result.logMessage}")
client.exhaustResponse(moveMethod.getResponseBodyAsStream())
return result
} catch (exception: Exception) {
result = RemoteOperationResult<Unit>(exception)
Timber.e(exception, "Rename $oldRemotePath to $newName: ${result.logMessage}")
return result
}
}
/**
* Checks if a file with the new name already exists.
*
* @return 'True' if the target path is already used by an existing file.
*/
private fun targetPathIsUsed(client: OwnCloudClient): Boolean {
val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation(newRemotePath, true)
val exists = checkPathExistenceRemoteOperation.execute(client)
return exists.isSuccess
}
private fun isSuccess(status: Int) = status.isOneOf(HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
companion object {
private const val RENAME_READ_TIMEOUT = 10_000L
private const val RENAME_CONNECTION_TIMEOUT = 5_000L
}
}

View File

@ -0,0 +1,68 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.ContentUriRequestBody
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.isOneOf
import timber.log.Timber
import java.net.URL
class UploadFileFromContentUriOperation(
private val uploadPath: String,
private val lastModified: String,
private val requestBody: ContentUriRequestBody
) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
val putMethod = PutMethod(URL(client.userFilesWebDavUri.toString() + WebdavUtils.encodePath(uploadPath)), requestBody).apply {
retryOnConnectionFailure = false
addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, requestBody.contentLength().toString())
addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, lastModified)
}
return try {
val status = client.executeHttpMethod(putMethod)
if (isSuccess(status)) {
RemoteOperationResult<Unit>(RemoteOperationResult.ResultCode.OK).apply { data = Unit }
} else {
RemoteOperationResult<Unit>(putMethod)
}
} catch (e: Exception) {
val result = RemoteOperationResult<Unit>(e)
Timber.e(e, "Upload from content uri failed : ${result.logMessage}")
result
}
}
fun isSuccess(status: Int): Boolean {
return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
}
}

View File

@ -0,0 +1,148 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2023 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.FileRequestBody
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.network.WebdavUtils
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.isOneOf
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import timber.log.Timber
import java.io.File
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean
/**
* Remote operation performing the upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
* @author Abel García de Prada
* @author Juan Carlos Garrote Gascón
*/
open class UploadFileFromFileSystemOperation(
val localPath: String,
val remotePath: String,
val mimeType: String,
val lastModifiedTimestamp: String,
val requiredEtag: String?,
val spaceWebDavUrl: String? = null,
) : RemoteOperation<Unit>() {
protected val cancellationRequested = AtomicBoolean(false)
protected var putMethod: PutMethod? = null
protected val dataTransferListener: MutableSet<OnDatatransferProgressListener> = HashSet()
protected var fileRequestBody: FileRequestBody? = null
var etag: String = ""
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
var result: RemoteOperationResult<Unit>
try {
if (cancellationRequested.get()) {
// the operation was cancelled before getting it's turn to be executed in the queue of uploads
result = RemoteOperationResult<Unit>(OperationCancelledException())
Timber.i("Upload of $localPath to $remotePath has been cancelled")
} else {
// perform the upload
result = uploadFile(client)
Timber.i("Upload of $localPath to $remotePath: ${result.logMessage}")
}
} catch (e: Exception) {
if (putMethod?.isAborted == true) {
result = RemoteOperationResult<Unit>(OperationCancelledException())
Timber.e(result.exception, "Upload of $localPath to $remotePath has been aborted with this message: ${result.logMessage}")
} else {
result = RemoteOperationResult<Unit>(e)
Timber.e(result.exception, "Upload of $localPath to $remotePath has failed with this message: ${result.logMessage}")
}
}
return result
}
@Throws(Exception::class)
protected open fun uploadFile(client: OwnCloudClient): RemoteOperationResult<Unit> {
val fileToUpload = File(localPath)
val mediaType: MediaType? = mimeType.toMediaTypeOrNull()
fileRequestBody = FileRequestBody(fileToUpload, mediaType).also {
synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) }
}
val baseStringUrl = spaceWebDavUrl ?: client.userFilesWebDavUri.toString()
putMethod = PutMethod(URL(baseStringUrl + WebdavUtils.encodePath(remotePath)), fileRequestBody!!).apply {
retryOnConnectionFailure = false
if (!requiredEtag.isNullOrBlank()) {
addRequestHeader(HttpConstants.IF_MATCH_HEADER, requiredEtag)
}
addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileToUpload.length().toString())
addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, lastModifiedTimestamp)
}
val status = client.executeHttpMethod(putMethod)
return if (isSuccess(status)) {
etag = WebdavUtils.getEtagFromResponse(putMethod)
// Get rid of extra quotas
etag = etag.replace("\"", "")
if (etag.isEmpty()) {
Timber.e("Could not read eTag from response uploading %s", localPath)
} else {
Timber.d("File uploaded successfully. New etag for file ${fileToUpload.name} is $etag")
}
RemoteOperationResult<Unit>(ResultCode.OK).apply { data = Unit }
} else { // synchronization failed
RemoteOperationResult<Unit>(putMethod)
}
}
fun addDataTransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListener) { dataTransferListener.add(listener) }
fileRequestBody?.addDatatransferProgressListener(listener)
}
fun removeDataTransferProgressListener(listener: OnDatatransferProgressListener) {
synchronized(dataTransferListener) { dataTransferListener.remove(listener) }
fileRequestBody?.removeDatatransferProgressListener(listener)
}
fun cancel() {
synchronized(cancellationRequested) {
cancellationRequested.set(true)
putMethod?.abort()
}
}
fun isSuccess(status: Int): Boolean {
return status.isOneOf(HttpConstants.HTTP_OK, HttpConstants.HTTP_CREATED, HttpConstants.HTTP_NO_CONTENT)
}
}

View File

@ -1,181 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod;
import com.owncloud.android.lib.common.network.FileRequestBody;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import okhttp3.MediaType;
import timber.log.Timber;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author masensio
* @author David González Verdugo
*/
public class UploadRemoteFileOperation extends RemoteOperation {
protected final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
protected String mLocalPath;
protected String mRemotePath;
protected String mMimeType;
protected String mFileLastModifTimestamp;
protected PutMethod mPutMethod = null;
protected String mRequiredEtag = null;
protected Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
protected FileRequestBody mFileRequestBody = null;
public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType,
String fileLastModifTimestamp) {
mLocalPath = localPath;
mRemotePath = remotePath;
mMimeType = mimeType;
mFileLastModifTimestamp = fileLastModifTimestamp;
}
public UploadRemoteFileOperation(String localPath, String remotePath, String mimeType,
String requiredEtag, String fileLastModifTimestamp) {
this(localPath, remotePath, mimeType, fileLastModifTimestamp);
mRequiredEtag = requiredEtag;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result;
try {
if (mCancellationRequested.get()) {
// the operation was cancelled before getting it's turn to be executed in the queue of uploads
result = new RemoteOperationResult<>(new OperationCancelledException());
} else {
// perform the upload
result = uploadFile(client);
Timber.i("Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
}
} catch (Exception e) {
if (mPutMethod != null && mPutMethod.isAborted()) {
result = new RemoteOperationResult<>(new OperationCancelledException());
Timber.e(result.getException(),
"Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
} else {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Upload of " + mLocalPath + " to " + mRemotePath + ": " + result.getLogMessage());
}
}
return result;
}
protected RemoteOperationResult<?> uploadFile(OwnCloudClient client) throws Exception {
File fileToUpload = new File(mLocalPath);
MediaType mediaType = MediaType.parse(mMimeType);
mFileRequestBody = new FileRequestBody(fileToUpload, mediaType);
synchronized (mDataTransferListeners) {
mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners);
}
mPutMethod = new PutMethod(
new URL(client.getUserFilesWebDavUri() + WebdavUtils.encodePath(mRemotePath)), mFileRequestBody);
mPutMethod.setRetryOnConnectionFailure(false);
if (mRequiredEtag != null && mRequiredEtag.length() > 0) {
mPutMethod.addRequestHeader(HttpConstants.IF_MATCH_HEADER, mRequiredEtag);
}
mPutMethod.addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, String.valueOf(fileToUpload.length()));
mPutMethod.addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, mFileLastModifTimestamp);
int status = client.executeHttpMethod(mPutMethod);
if (isSuccess(status)) {
return new RemoteOperationResult<>(OK);
} else { // synchronization failed
return new RemoteOperationResult<>(mPutMethod);
}
}
public Set<OnDatatransferProgressListener> getDataTransferListeners() {
return mDataTransferListeners;
}
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
if (mFileRequestBody != null) {
mFileRequestBody.addDatatransferProgressListener(listener);
}
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
if (mFileRequestBody != null) {
mFileRequestBody.removeDatatransferProgressListener(listener);
}
}
public void cancel() {
synchronized (mCancellationRequested) {
mCancellationRequested.set(true);
if (mPutMethod != null) {
mPutMethod.abort();
}
}
}
public boolean isSuccess(int status) {
return ((status == HttpConstants.HTTP_OK || status == HttpConstants.HTTP_CREATED ||
status == HttpConstants.HTTP_NO_CONTENT));
}
}

View File

@ -0,0 +1,122 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2022 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod
import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody
import com.owncloud.android.lib.common.operations.OperationCancelledException
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.resources.files.FileUtils.MODE_READ_ONLY
import com.owncloud.android.lib.resources.files.UploadFileFromFileSystemOperation
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import timber.log.Timber
import java.io.File
import java.io.RandomAccessFile
import java.net.URL
import java.nio.channels.FileChannel
import java.util.concurrent.TimeUnit
import kotlin.math.ceil
/**
* Remote operation performing the chunked upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author David González Verdugo
* @author Abel García de Prada
*/
class ChunkedUploadFromFileSystemOperation(
private val transferId: String,
localPath: String,
remotePath: String,
mimeType: String,
lastModifiedTimestamp: String,
requiredEtag: String?,
) : UploadFileFromFileSystemOperation(
localPath = localPath,
remotePath = remotePath,
mimeType = mimeType,
lastModifiedTimestamp = lastModifiedTimestamp,
requiredEtag = requiredEtag
) {
@Throws(Exception::class)
override fun uploadFile(client: OwnCloudClient): RemoteOperationResult<Unit> {
lateinit var result: RemoteOperationResult<Unit>
val fileToUpload = File(localPath)
val mediaType: MediaType? = mimeType.toMediaTypeOrNull()
val raf = RandomAccessFile(fileToUpload, MODE_READ_ONLY)
val channel: FileChannel = raf.channel
val fileRequestBody = ChunkFromFileRequestBody(fileToUpload, mediaType, channel).also {
synchronized(dataTransferListener) { it.addDatatransferProgressListeners(dataTransferListener) }
}
val uriPrefix = client.uploadsWebDavUri.toString() + File.separator + transferId
val totalLength = fileToUpload.length()
val chunkCount = ceil(totalLength.toDouble() / CHUNK_SIZE).toLong()
var offset: Long = 0
for (chunkIndex in 0..chunkCount) {
fileRequestBody.setOffset(offset)
if (cancellationRequested.get()) {
result = RemoteOperationResult<Unit>(OperationCancelledException())
break
} else {
putMethod = PutMethod(URL(uriPrefix + File.separator + chunkIndex), fileRequestBody).apply {
if (chunkIndex == chunkCount - 1) {
// Added a high timeout to the last chunk due to when the last chunk
// arrives to the server with the last PUT, all chunks get assembled
// within that PHP request, so last one takes longer.
setReadTimeout(LAST_CHUNK_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
}
}
val status = client.executeHttpMethod(putMethod)
Timber.d("Upload of $localPath to $remotePath, chunk index $chunkIndex, count $chunkCount, HTTP result status $status")
if (isSuccess(status)) {
result = RemoteOperationResult<Unit>(ResultCode.OK)
} else {
result = RemoteOperationResult<Unit>(putMethod)
break
}
}
offset += CHUNK_SIZE
}
channel.close()
raf.close()
return result
}
companion object {
const val CHUNK_SIZE = 10_240_000L // 10 MB
private const val LAST_CHUNK_TIMEOUT = 900_000 // 15 mins.
}
}

View File

@ -1,134 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.chunks;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.methods.webdav.PutMethod;
import com.owncloud.android.lib.common.network.ChunkFromFileRequestBody;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
import okhttp3.MediaType;
import timber.log.Timber;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;
import static com.owncloud.android.lib.common.http.HttpConstants.IF_MATCH_HEADER;
import static com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode.OK;
/**
* Remote operation performing the chunked upload of a remote file to the ownCloud server.
*
* @author David A. Velasco
* @author David González Verdugo
*/
public class ChunkedUploadRemoteFileOperation extends UploadRemoteFileOperation {
public static final long CHUNK_SIZE = 1024000;
private static final int LAST_CHUNK_TIMEOUT = 900000; //15 mins.
private String mTransferId;
public ChunkedUploadRemoteFileOperation(String transferId, String localPath, String remotePath, String mimeType,
String requiredEtag, String fileLastModifTimestamp) {
super(localPath, remotePath, mimeType, requiredEtag, fileLastModifTimestamp);
mTransferId = transferId;
}
@Override
protected RemoteOperationResult uploadFile(OwnCloudClient client) throws Exception {
int status;
RemoteOperationResult result = null;
FileChannel channel;
RandomAccessFile raf;
File fileToUpload = new File(mLocalPath);
MediaType mediaType = MediaType.parse(mMimeType);
raf = new RandomAccessFile(fileToUpload, "r");
channel = raf.getChannel();
mFileRequestBody = new ChunkFromFileRequestBody(fileToUpload, mediaType, channel, CHUNK_SIZE);
synchronized (mDataTransferListeners) {
mFileRequestBody.addDatatransferProgressListeners(mDataTransferListeners);
}
long offset = 0;
String uriPrefix = client.getUploadsWebDavUri() + File.separator + mTransferId;
long totalLength = fileToUpload.length();
long chunkCount = (long) Math.ceil((double) totalLength / CHUNK_SIZE);
for (int chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++, offset += CHUNK_SIZE) {
((ChunkFromFileRequestBody) mFileRequestBody).setOffset(offset);
if (mCancellationRequested.get()) {
result = new RemoteOperationResult<>(new OperationCancelledException());
break;
} else {
mPutMethod = new PutMethod(new URL(uriPrefix + File.separator + chunkIndex), mFileRequestBody);
if (mRequiredEtag != null && mRequiredEtag.length() > 0) {
mPutMethod.addRequestHeader(IF_MATCH_HEADER, "\"" + mRequiredEtag + "\"");
}
if (chunkIndex == chunkCount - 1) {
// Added a high timeout to the last chunk due to when the last chunk
// arrives to the server with the last PUT, all chunks get assembled
// within that PHP request, so last one takes longer.
mPutMethod.setReadTimeout(LAST_CHUNK_TIMEOUT, TimeUnit.MILLISECONDS);
}
status = client.executeHttpMethod(mPutMethod);
Timber.d("Upload of " + mLocalPath + " to " + mRemotePath +
", chunk index " + chunkIndex + ", count " + chunkCount +
", HTTP result status " + status);
if (isSuccess(status)) {
result = new RemoteOperationResult<>(OK);
} else {
result = new RemoteOperationResult<>(mPutMethod);
break;
}
}
}
if (channel != null) {
channel.close();
}
if (raf != null) {
raf.close();
}
return result;
}
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2021 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -21,30 +21,38 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.resources.files.chunks
package com.owncloud.android.lib.resources.files.chunks; import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation; import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.methods.webdav.MoveMethod
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation
/** /**
* Remote operation to move the file built from chunks after uploading it * Remote operation to move the file built from chunks after uploading it
* *
* @author David González Verdugo * @author David González Verdugo
* @author Abel García de Prada
*/ */
public class MoveRemoteChunksFileOperation extends MoveRemoteFileOperation { class MoveRemoteChunksFileOperation(
sourceRemotePath: String,
targetRemotePath: String,
private val fileLastModificationTimestamp: String,
private val fileLength: Long
) : MoveRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
) {
/** override fun getSrcWebDavUriForClient(client: OwnCloudClient): Uri = client.uploadsWebDavUri
* Constructor.
* override fun addRequestHeaders(moveMethod: MoveMethod) {
* @param srcRemotePath Remote path of the file/folder to move. super.addRequestHeaders(moveMethod)
* @param targetRemotePath Remove path desired for the file/folder after moving it.
* @param overwrite moveMethod.apply {
*/ addRequestHeader(HttpConstants.OC_X_OC_MTIME_HEADER, fileLastModificationTimestamp)
public MoveRemoteChunksFileOperation(String srcRemotePath, String targetRemotePath, boolean overwrite, addRequestHeader(HttpConstants.OC_TOTAL_LENGTH_HEADER, fileLength.toString())
String fileLastModifTimestamp, long fileLength) { }
super(srcRemotePath, targetRemotePath, overwrite);
moveChunkedFile = true;
mFileLastModifTimestamp = fileLastModifTimestamp;
mFileLength = fileLength;
} }
} }

View File

@ -22,20 +22,11 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.resources.files.chunks
package com.owncloud.android.lib.resources.files.chunks; import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation; class RemoveRemoteChunksFolderOperation(remotePath: String) : RemoveRemoteFileOperation(remotePath) {
override fun getSrcWebDavUriForClient(client: OwnCloudClient): String = client.uploadsWebDavUri.toString()
public class RemoveRemoteChunksFolderOperation extends RemoveRemoteFileOperation { }
/**
* Constructor
*
* @param remotePath RemotePath of the remote file or folder to remove from the server
*/
public RemoveRemoteChunksFolderOperation(String remotePath) {
super(remotePath);
removeChunksFolder = true;
}
}

View File

@ -0,0 +1,41 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2021 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.files.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service
interface ChunkService : Service {
fun removeFile(
remotePath: String
): RemoteOperationResult<Unit>
fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
fileLastModificationTimestamp: String,
fileLength: Long
): RemoteOperationResult<Unit>
}

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2023 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,7 +25,62 @@ package com.owncloud.android.lib.resources.files.services
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.Service import com.owncloud.android.lib.resources.Service
import com.owncloud.android.lib.resources.files.RemoteFile
interface FileService: Service { interface FileService : Service {
fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> fun checkPathExistence(
path: String,
isUserLogged: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Boolean>
fun copyFile(
sourceRemotePath: String,
targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<String?>
fun createFolder(
remotePath: String,
createFullPath: Boolean,
isChunkFolder: Boolean = false,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
fun downloadFile(
remotePath: String,
localTempPath: String
): RemoteOperationResult<Unit>
fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
spaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<Unit>
fun readFile(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<RemoteFile>
fun refreshFolder(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<ArrayList<RemoteFile>>
fun removeFile(
remotePath: String,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
fun renameFile(
oldName: String,
oldRemotePath: String,
newName: String,
isFolder: Boolean,
spaceWebDavUrl: String? = null,
): RemoteOperationResult<Unit>
} }

View File

@ -1,5 +1,5 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH. * Copyright (C) 2021 ownCloud GmbH.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -21,29 +21,30 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.resources.files
import android.net.Uri package com.owncloud.android.lib.resources.files.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import okhttp3.HttpUrl import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.chunks.MoveRemoteChunksFileOperation
import com.owncloud.android.lib.resources.files.chunks.RemoveRemoteChunksFolderOperation
import com.owncloud.android.lib.resources.files.services.ChunkService
class RemoteFileUtil { class OCChunkService(override val client: OwnCloudClient) : ChunkService {
companion object {
/** override fun removeFile(remotePath: String): RemoteOperationResult<Unit> =
* Retrieves a relative path from a remote file url RemoveRemoteChunksFolderOperation(remotePath = remotePath).execute(client)
*
* override fun moveFile(
* Example: url:port/remote.php/dav/files/username/Documents/text.txt => /Documents/text.txt sourceRemotePath: String,
* targetRemotePath: String,
* @param url remote file url fileLastModificationTimestamp: String,
* @param userId file owner fileLength: Long
* @return remote relative path of the file ): RemoteOperationResult<Unit> =
*/ MoveRemoteChunksFileOperation(
fun getRemotePathFromUrl(url: HttpUrl, userId: String): String? { sourceRemotePath = sourceRemotePath,
val davFilesPath = OwnCloudClient.WEBDAV_FILES_PATH_4_0 + userId targetRemotePath = targetRemotePath,
val absoluteDavPath = Uri.decode(url.encodedPath) fileLastModificationTimestamp = fileLastModificationTimestamp,
val pathToOc = absoluteDavPath.split(davFilesPath)[0] fileLength = fileLength,
return absoluteDavPath.replace(pathToOc + davFilesPath, "") ).execute(client)
}
}
} }

View File

@ -1,34 +1,143 @@
/** /* ownCloud Android Library is available under MIT license
* ownCloud Android client application * Copyright (C) 2023 ownCloud GmbH.
* *
* @author Abel García de Prada * Permission is hereby granted, free of charge, to any person obtaining a copy
* Copyright (C) 2020 ownCloud GmbH. * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* *
* This program is free software: you can redistribute it and/or modify * The above copyright notice and this permission notice shall be included in
* it under the terms of the GNU General Public License version 2, * all copies or substantial portions of the Software.
* as published by the Free Software Foundation.
* *
* This program is distributed in the hope that it will be useful, * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* GNU General Public License for more details. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.owncloud.android.lib.resources.files.services.implementation package com.owncloud.android.lib.resources.files.services.implementation
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation
import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation
import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation
import com.owncloud.android.lib.resources.files.MoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation
import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation
import com.owncloud.android.lib.resources.files.RemoteFile
import com.owncloud.android.lib.resources.files.RemoveRemoteFileOperation
import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation
import com.owncloud.android.lib.resources.files.services.FileService import com.owncloud.android.lib.resources.files.services.FileService
class OCFileService(override val client: OwnCloudClient) : class OCFileService(override val client: OwnCloudClient) : FileService {
FileService { override fun checkPathExistence(
override fun checkPathExistence(path: String, isUserLogged: Boolean): RemoteOperationResult<Boolean> = path: String,
isUserLogged: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Boolean> =
CheckPathExistenceRemoteOperation( CheckPathExistenceRemoteOperation(
remotePath = path, remotePath = path,
isUserLogged = isUserLogged isUserLoggedIn = isUserLogged,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun copyFile(
sourceRemotePath: String,
targetRemotePath: String,
sourceSpaceWebDavUrl: String?,
targetSpaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<String?> =
CopyRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
sourceSpaceWebDavUrl = sourceSpaceWebDavUrl,
targetSpaceWebDavUrl = targetSpaceWebDavUrl,
forceOverride = replace,
).execute(client)
override fun createFolder(
remotePath: String,
createFullPath: Boolean,
isChunkFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
CreateRemoteFolderOperation(
remotePath = remotePath,
createFullPath = createFullPath,
isChunksFolder = isChunkFolder,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun downloadFile(
remotePath: String,
localTempPath: String
): RemoteOperationResult<Unit> =
DownloadRemoteFileOperation(
remotePath = remotePath,
localFolderPath = localTempPath
).execute(client)
override fun moveFile(
sourceRemotePath: String,
targetRemotePath: String,
spaceWebDavUrl: String?,
replace: Boolean,
): RemoteOperationResult<Unit> =
MoveRemoteFileOperation(
sourceRemotePath = sourceRemotePath,
targetRemotePath = targetRemotePath,
spaceWebDavUrl = spaceWebDavUrl,
forceOverride = replace,
).execute(client)
override fun readFile(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<RemoteFile> =
ReadRemoteFileOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun refreshFolder(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<ArrayList<RemoteFile>> =
ReadRemoteFolderOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun removeFile(
remotePath: String,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
RemoveRemoteFileOperation(
remotePath = remotePath,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client)
override fun renameFile(
oldName: String,
oldRemotePath: String,
newName: String,
isFolder: Boolean,
spaceWebDavUrl: String?,
): RemoteOperationResult<Unit> =
RenameRemoteFileOperation(
oldName = oldName,
oldRemotePath = oldRemotePath,
newName = newName,
isFolder = isFolder,
spaceWebDavUrl = spaceWebDavUrl,
).execute(client) ).execute(client)
} }

View File

@ -55,6 +55,7 @@ class GetOIDCDiscoveryRemoteOperation : RemoteOperation<OIDCDiscoveryResponse>()
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE) addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
} }
getMethod.followRedirects = true
val status = client.executeHttpMethod(getMethod) val status = client.executeHttpMethod(getMethod)
val responseBody = getMethod.getResponseBodyAsString() val responseBody = getMethod.getResponseBodyAsString()

View File

@ -39,7 +39,8 @@ sealed class TokenRequestParams(
clientAuth: String, clientAuth: String,
grantType: String, grantType: String,
val authorizationCode: String, val authorizationCode: String,
val redirectUri: String val redirectUri: String,
val codeVerifier: String,
) : TokenRequestParams(tokenEndpoint, clientAuth, grantType) { ) : TokenRequestParams(tokenEndpoint, clientAuth, grantType) {
override fun toRequestBody(): RequestBody = override fun toRequestBody(): RequestBody =
@ -47,6 +48,7 @@ sealed class TokenRequestParams(
.add(HttpConstants.OAUTH_HEADER_AUTHORIZATION_CODE, authorizationCode) .add(HttpConstants.OAUTH_HEADER_AUTHORIZATION_CODE, authorizationCode)
.add(HttpConstants.OAUTH_HEADER_GRANT_TYPE, grantType) .add(HttpConstants.OAUTH_HEADER_GRANT_TYPE, grantType)
.add(HttpConstants.OAUTH_HEADER_REDIRECT_URI, redirectUri) .add(HttpConstants.OAUTH_HEADER_REDIRECT_URI, redirectUri)
.add(HttpConstants.OAUTH_HEADER_CODE_VERIFIER, codeVerifier)
.build() .build()
} }

View File

@ -31,13 +31,13 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class OIDCDiscoveryResponse( data class OIDCDiscoveryResponse(
val authorization_endpoint: String, val authorization_endpoint: String,
val check_session_iframe: String, val check_session_iframe: String?,
val end_session_endpoint: String, val end_session_endpoint: String?,
val issuer: String, val issuer: String,
val registration_endpoint: String, val registration_endpoint: String?,
val response_types_supported: List<String>, val response_types_supported: List<String>,
val scopes_supported: List<String>, val scopes_supported: List<String>?,
val token_endpoint: String, val token_endpoint: String,
val token_endpoint_auth_methods_supported: List<String>, val token_endpoint_auth_methods_supported: List<String>?,
val userinfo_endpoint: String, val userinfo_endpoint: String?,
) )

View File

@ -2,7 +2,8 @@
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* Copyright (C) 2020 ownCloud GmbH * @author Fernando Sanz Velasco
* Copyright (C) 2021 ownCloud GmbH
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -27,14 +28,23 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.RemoteShare.Companion.INIT_EXPIRATION_DATE_IN_MILLIS import com.owncloud.android.lib.resources.shares.RemoteShare.Companion.INIT_EXPIRATION_DATE_IN_MILLIS
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okhttp3.FormBody import okhttp3.FormBody
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL import java.net.URL
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -46,6 +56,7 @@ import java.util.Locale
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Fernando Sanz Velasco
*/ */
/** /**
@ -70,101 +81,131 @@ class CreateRemoteShareOperation(
private val shareType: ShareType, private val shareType: ShareType,
private val shareWith: String, private val shareWith: String,
private val permissions: Int private val permissions: Int
) : RemoteOperation<ShareParserResult>() { ) : RemoteOperation<ShareResponse>() {
var name = "" // Name to set for the public link var name = "" // Name to set for the public link
var password: String = "" // Password to set for the public link var password: String = "" // Password to set for the public link
var expirationDateInMillis: Long = INIT_EXPIRATION_DATE_IN_MILLIS // Expiration date to set for the public link var expirationDateInMillis: Long = INIT_EXPIRATION_DATE_IN_MILLIS // Expiration date to set for the public link
var publicUpload: Boolean = false // Upload permissions for the public link (only folders)
var retrieveShareDetails = false // To retrieve more info about the just created share var retrieveShareDetails = false // To retrieve more info about the just created share
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareParserResult> { private fun buildRequestUri(baseUri: Uri) =
var result: RemoteOperationResult<ShareParserResult> baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
try { private fun parseResponse(response: String): ShareResponse {
val formBodyBuilder = FormBody.Builder() val moshi = Moshi.Builder().build()
.add(PARAM_PATH, remoteFilePath) val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
.add(PARAM_SHARE_TYPE, shareType.value.toString()) val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
.add(PARAM_SHARE_WITH, shareWith) val remoteShare = adapter.fromJson(response)?.ocs?.data?.toRemoteShare()
return ShareResponse(remoteShare?.let { listOf(it) } ?: listOf())
}
if (name.isNotEmpty()) { private fun onResultUnsuccessful(
formBodyBuilder.add(PARAM_NAME, name) method: PostMethod,
} response: String?,
status: Int
): RemoteOperationResult<ShareResponse> {
Timber.e("Failed response while while creating new remote share operation ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
if (expirationDateInMillis > INIT_EXPIRATION_DATE_IN_MILLIS) { private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> {
val dateFormat = SimpleDateFormat(FORMAT_EXPIRATION_DATE, Locale.getDefault()) val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK)
val expirationDate = Calendar.getInstance() Timber.d("Successful response: $response")
expirationDate.timeInMillis = expirationDateInMillis result.data = parseResponse(response!!)
val formattedExpirationDate = dateFormat.format(expirationDate.time) Timber.d("*** Creating new remote share operation completed ")
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
}
if (publicUpload) { val emptyShare = result.data.shares.first()
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString())
}
if (password.isNotEmpty()) {
formBodyBuilder.add(PARAM_PASSWORD, password)
}
if (RemoteShare.DEFAULT_PERMISSION != permissions) {
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
}
val requestUri = client.baseUri return if (retrieveShareDetails) {
val uriBuilder = requestUri.buildUpon() // retrieve more info - PUT only returns the index of the new share
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH) GetRemoteShareOperation(emptyShare.id).execute(client)
} else {
result
}
}
val postMethod = PostMethod(URL(uriBuilder.build().toString()), formBodyBuilder.build()) private fun createFormBody(): FormBody {
val formBodyBuilder = FormBody.Builder()
.add(PARAM_PATH, remoteFilePath)
.add(PARAM_SHARE_TYPE, shareType.value.toString())
.add(PARAM_SHARE_WITH, shareWith)
postMethod.setRequestHeader(HttpConstants.CONTENT_TYPE_HEADER, HttpConstants.CONTENT_TYPE_URLENCODED_UTF8) if (name.isNotEmpty()) {
postMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE) formBodyBuilder.add(PARAM_NAME, name)
}
if (expirationDateInMillis > INIT_EXPIRATION_DATE_IN_MILLIS) {
val dateFormat = SimpleDateFormat(FORMAT_EXPIRATION_DATE, Locale.getDefault())
val expirationDate = Calendar.getInstance()
expirationDate.timeInMillis = expirationDateInMillis
val formattedExpirationDate = dateFormat.format(expirationDate.time)
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
}
if (password.isNotEmpty()) {
formBodyBuilder.add(PARAM_PASSWORD, password)
}
if (RemoteShare.DEFAULT_PERMISSION != permissions) {
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
}
return formBodyBuilder.build()
}
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
val postMethod = PostMethod(URL(requestUri.toString()), createFormBody()).apply {
setRequestHeader(HttpConstants.CONTENT_TYPE_HEADER, HttpConstants.CONTENT_TYPE_URLENCODED_UTF8)
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
return try {
val status = client.executeHttpMethod(postMethod) val status = client.executeHttpMethod(postMethod)
val response = postMethod.getResponseBodyAsString()
val parser = ShareToRemoteOperationResultParser(
ShareXMLParser()
)
if (isSuccess(status)) { if (isSuccess(status)) {
parser.oneOrMoreSharesRequired = true onRequestSuccessful(response)
parser.ownCloudVersion = client.ownCloudVersion
parser.serverBaseUri = client.baseUri
result = parser.parse(postMethod.getResponseBodyAsString())
if (result.isSuccess && retrieveShareDetails) {
// retrieve more info - POST only returns the index of the new share
val emptyShare = result.data.shares[0]
val getInfo = GetRemoteShareOperation(
emptyShare.id
)
result = getInfo.execute(client)
}
} else { } else {
result = parser.parse(postMethod.getResponseBodyAsString()) onResultUnsuccessful(postMethod, response, status)
} }
} catch (e: Exception) { } catch (e: Exception) {
result = RemoteOperationResult(e) Timber.e(e, "Exception while creating new remote share operation ")
Timber.e(e, "Exception while Creating New Share") RemoteOperationResult(e)
} }
return result
} }
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object { companion object {
//OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
//Arguments - names
private const val PARAM_NAME = "name" private const val PARAM_NAME = "name"
private const val PARAM_PASSWORD = "password"
private const val PARAM_EXPIRATION_DATE = "expireDate" private const val PARAM_EXPIRATION_DATE = "expireDate"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
private const val PARAM_PATH = "path" private const val PARAM_PATH = "path"
private const val PARAM_SHARE_TYPE = "shareType" private const val PARAM_SHARE_TYPE = "shareType"
private const val PARAM_SHARE_WITH = "shareWith" private const val PARAM_SHARE_WITH = "shareWith"
private const val PARAM_PASSWORD = "password"
private const val PARAM_PERMISSIONS = "permissions" private const val PARAM_PERMISSIONS = "permissions"
//Arguments - constant values
private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd" private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"
} }
} }

View File

@ -1,94 +0,0 @@
/* ownCloud Android Library is available under MIT license
* @author David A. Velasco
* @author David González Verdugo
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.shares;
import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.http.HttpConstants;
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import timber.log.Timber;
import java.net.URL;
/**
* Get the data about a Share resource, known its remote ID.
*
* @author David A. Velasco
* @author David González Verdugo
*/
public class GetRemoteShareOperation extends RemoteOperation<ShareParserResult> {
private String mRemoteId;
public GetRemoteShareOperation(String remoteId) {
mRemoteId = remoteId;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult<ShareParserResult> result;
try {
Uri requestUri = client.getBaseUri();
Uri.Builder uriBuilder = requestUri.buildUpon();
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH);
uriBuilder.appendEncodedPath(mRemoteId);
GetMethod getMethod = new GetMethod(new URL(uriBuilder.build().toString()));
getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);
int status = client.executeHttpMethod(getMethod);
if (isSuccess(status)) {
// Parse xml response and obtain the list of shares
ShareToRemoteOperationResultParser parser = new ShareToRemoteOperationResultParser(
new ShareXMLParser()
);
parser.setOneOrMoreSharesRequired(true);
parser.setOwnCloudVersion(client.getOwnCloudVersion());
parser.setServerBaseUri(client.getBaseUri());
result = parser.parse(getMethod.getResponseBodyAsString());
} else {
result = new RemoteOperationResult<>(getMethod);
}
} catch (Exception e) {
result = new RemoteOperationResult<>(e);
Timber.e(e, "Exception while getting remote shares");
}
return result;
}
private boolean isSuccess(int status) {
return (status == HttpConstants.HTTP_OK);
}
}

View File

@ -0,0 +1,116 @@
/* ownCloud Android Library is available under MIT license
* @author Fernando Sanz Velasco
* Copyright (C) 2021 ownCloud GmbH
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL
class GetRemoteShareOperation(private val remoteId: String) : RemoteOperation<ShareResponse>() {
private fun buildRequestUri(baseUri: Uri) =
baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendEncodedPath(remoteId)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val listOfShareItemType: Type = Types.newParameterizedType(List::class.java, ShareItem::class.java)
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, listOfShareItemType)
val adapter: JsonAdapter<CommonOcsResponse<List<ShareItem>>> = moshi.adapter(commonOcsType)
return adapter.fromJson(response)?.ocs?.data?.let { listOfShareItems ->
ShareResponse(listOfShareItems.map { shareItem ->
shareItem.toRemoteShare()
})
}
}
private fun onResultUnsuccessful(
method: GetMethod,
response: String?,
status: Int
): RemoteOperationResult<ShareResponse> {
Timber.e("Failed response while while getting remote shares ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> {
val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Get Users or groups completed ")
return result
}
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
val getMethod = GetMethod(URL(requestUri.toString())).apply {
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
return try {
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (!isSuccess(status)) {
onResultUnsuccessful(getMethod, response, status)
} else {
onRequestSuccessful(response)
}
} catch (e: Exception) {
Timber.e(e, "Exception while getting remote shares")
RemoteOperationResult(e)
}
}
private fun isSuccess(status: Int) = status == HttpConstants.HTTP_OK
companion object {
//OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
}
}

View File

@ -32,6 +32,8 @@ package com.owncloud.android.lib.resources.shares
import android.net.Uri import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
@ -97,11 +99,11 @@ class GetRemoteShareesOperation
.appendQueryParameter(PARAM_PER_PAGE, perPage.toString()) .appendQueryParameter(PARAM_PER_PAGE, perPage.toString())
.build() .build()
private fun parseResponse(response: String): ShareeOcsResponse? { private fun parseResponse(response: String?): ShareeOcsResponse? {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java) val type: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareeOcsResponse::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type) val adapter: JsonAdapter<CommonOcsResponse<ShareeOcsResponse>> = moshi.adapter(type)
return adapter.fromJson(response)!!.ocs.data return response?.let { adapter.fromJson(it)?.ocs?.data }
} }
private fun onResultUnsuccessful( private fun onResultUnsuccessful(
@ -121,7 +123,7 @@ class GetRemoteShareesOperation
private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> { private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareeOcsResponse> {
val result = RemoteOperationResult<ShareeOcsResponse>(OK) val result = RemoteOperationResult<ShareeOcsResponse>(OK)
Timber.d("Successful response: $response") Timber.d("Successful response: $response")
result.data = parseResponse(response!!) result.data = parseResponse(response)
Timber.d("*** Get Users or groups completed ") Timber.d("*** Get Users or groups completed ")
return result return result
} }
@ -136,10 +138,10 @@ class GetRemoteShareesOperation
val status = client.executeHttpMethod(getMethod) val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString() val response = getMethod.getResponseBodyAsString()
if (!isSuccess(status)) { if (isSuccess(status)) {
onResultUnsuccessful(getMethod, response, status)
} else {
onRequestSuccessful(response) onRequestSuccessful(response)
} else {
onResultUnsuccessful(getMethod, response, status)
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Exception while getting users/groups") Timber.e(e, "Exception while getting users/groups")
@ -155,14 +157,12 @@ class GetRemoteShareesOperation
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/sharees" // from OC 8.2 private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/sharees" // from OC 8.2
// Arguments - names // Arguments - names
private const val PARAM_FORMAT = "format"
private const val PARAM_ITEM_TYPE = "itemType" private const val PARAM_ITEM_TYPE = "itemType"
private const val PARAM_SEARCH = "search" private const val PARAM_SEARCH = "search"
private const val PARAM_PAGE = "page" // default = 1 private const val PARAM_PAGE = "page" // default = 1
private const val PARAM_PER_PAGE = "perPage" // default = 200 private const val PARAM_PER_PAGE = "perPage" // default = 200
// Arguments - constant values // Arguments - constant values
private const val VALUE_FORMAT = "json"
private const val VALUE_ITEM_TYPE = "file" // to get the server search for users / groups private const val VALUE_ITEM_TYPE = "file" // to get the server search for users / groups
} }
} }

View File

@ -2,7 +2,8 @@
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* Copyright (C) 2020 ownCloud GmbH. * @author Fernando Sanz Velasco
* Copyright (C) 2021 ownCloud GmbH
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -27,12 +28,21 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL import java.net.URL
/** /**
@ -43,6 +53,7 @@ import java.net.URL
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Fernando Sanz Velasco
*/ */
/** /**
@ -59,52 +70,82 @@ class GetRemoteSharesForFileOperation(
private val remoteFilePath: String, private val remoteFilePath: String,
private val reshares: Boolean, private val reshares: Boolean,
private val subfiles: Boolean private val subfiles: Boolean
) : RemoteOperation<ShareParserResult>() { ) : RemoteOperation<ShareResponse>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareParserResult> { private fun buildRequestUri(baseUri: Uri) =
var result: RemoteOperationResult<ShareParserResult> baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.appendQueryParameter(PARAM_PATH, remoteFilePath)
.appendQueryParameter(PARAM_RESHARES, reshares.toString())
.appendQueryParameter(PARAM_SUBFILES, subfiles.toString())
.build()
try { private fun parseResponse(response: String): ShareResponse? {
val moshi = Moshi.Builder().build()
val listOfShareItemType: Type = Types.newParameterizedType(List::class.java, ShareItem::class.java)
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, listOfShareItemType)
val adapter: JsonAdapter<CommonOcsResponse<List<ShareItem>>> = moshi.adapter(commonOcsType)
return adapter.fromJson(response)?.ocs?.data?.let { listOfShareItems ->
ShareResponse(listOfShareItems.map { shareItem ->
shareItem.toRemoteShare()
})
}
}
val requestUri = client.baseUri private fun onResultUnsuccessful(
val uriBuilder = requestUri.buildUpon() method: GetMethod,
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH) response: String?,
uriBuilder.appendQueryParameter(PARAM_PATH, remoteFilePath) status: Int
uriBuilder.appendQueryParameter(PARAM_RESHARES, reshares.toString()) ): RemoteOperationResult<ShareResponse> {
uriBuilder.appendQueryParameter(PARAM_SUBFILES, subfiles.toString()) Timber.e("Failed response while while getting remote shares for file operation ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
val getMethod = GetMethod(URL(uriBuilder.build().toString())) private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> {
val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Getting remote shares for file completed ")
Timber.d("Got ${result.data.shares.size} shares")
return result
}
getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE) override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
val status = client.executeHttpMethod(getMethod) val getMethod = GetMethod(URL(requestUri.toString())).apply {
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
if (isSuccess(status)) {
// Parse xml response and obtain the list of shares
val parser = ShareToRemoteOperationResultParser(
ShareXMLParser()
)
parser.ownCloudVersion = client.ownCloudVersion
parser.serverBaseUri = client.baseUri
result = parser.parse(getMethod.getResponseBodyAsString())
if (result.isSuccess) {
Timber.d("Got ${result.data.shares.size} shares")
}
} else {
result = RemoteOperationResult(getMethod)
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while getting shares")
} }
return result return try {
val status = client.executeHttpMethod(getMethod)
val response = getMethod.getResponseBodyAsString()
if (isSuccess(status)) {
onRequestSuccessful(response)
} else {
onResultUnsuccessful(getMethod, response, status)
}
} catch (e: Exception) {
Timber.e(e, "Exception while getting remote shares for file operation")
RemoteOperationResult(e)
}
} }
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object { companion object {
//OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
//Arguments - names
private const val PARAM_PATH = "path" private const val PARAM_PATH = "path"
private const val PARAM_RESHARES = "reshares" private const val PARAM_RESHARES = "reshares"
private const val PARAM_SUBFILES = "subfiles" private const val PARAM_SUBFILES = "subfiles"

View File

@ -24,7 +24,7 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
import java.io.File import com.owncloud.android.lib.resources.shares.responses.ItemType
/** /**
* Contains the data of a Share from the Share API * Contains the data of a Share from the Share API
@ -38,6 +38,7 @@ data class RemoteShare(
var shareWith: String = "", var shareWith: String = "",
var path: String = "", var path: String = "",
var token: String = "", var token: String = "",
var itemType: String = "",
var sharedWithDisplayName: String = "", var sharedWithDisplayName: String = "",
var sharedWithAdditionalInfo: String = "", var sharedWithAdditionalInfo: String = "",
var name: String = "", var name: String = "",
@ -46,7 +47,7 @@ data class RemoteShare(
var permissions: Int = DEFAULT_PERMISSION, var permissions: Int = DEFAULT_PERMISSION,
var sharedDate: Long = INIT_SHARED_DATE, var sharedDate: Long = INIT_SHARED_DATE,
var expirationDate: Long = INIT_EXPIRATION_DATE_IN_MILLIS, var expirationDate: Long = INIT_EXPIRATION_DATE_IN_MILLIS,
var isFolder: Boolean = path.endsWith(File.separator) var isFolder: Boolean = (itemType == ItemType.FOLDER.fileValue)
) { ) {
companion object { companion object {

View File

@ -2,7 +2,8 @@
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* Copyright (C) 2020 ownCloud GmbH. * @author Fernando Sanz Velasco
* Copyright (C) 2021 ownCloud GmbH
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -27,8 +28,11 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
@ -41,53 +45,66 @@ import java.net.URL
* @author masensio * @author masensio
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
*/ * @author Fernando Sanz Velasco
/**
* Constructor
* *
* @param remoteShareId Share ID * @param remoteShareId Share ID
*/ */
class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<ShareParserResult>() { class RemoveRemoteShareOperation(private val remoteShareId: String) : RemoteOperation<Unit>() {
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareParserResult> { private fun buildRequestUri(baseUri: Uri) =
var result: RemoteOperationResult<ShareParserResult> baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendEncodedPath(remoteShareId)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
try { private fun onResultUnsuccessful(
val requestUri = client.baseUri method: DeleteMethod,
val uriBuilder = requestUri.buildUpon() response: String?,
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH) status: Int
uriBuilder.appendEncodedPath(remoteShareId) ): RemoteOperationResult<Unit> {
Timber.e("Failed response while removing share ")
val deleteMethod = DeleteMethod( if (response != null) {
URL(uriBuilder.build().toString()) Timber.e("*** status code: $status; response message: $response")
) } else {
Timber.e("*** status code: $status")
deleteMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
val status = client.executeHttpMethod(deleteMethod)
if (isSuccess(status)) {
// Parse xml response and obtain the list of shares
val parser = ShareToRemoteOperationResultParser(
ShareXMLParser()
)
result = parser.parse(deleteMethod.getResponseBodyAsString())
Timber.d("Unshare $remoteShareId: ${result.logMessage}")
} else {
result = RemoteOperationResult(deleteMethod)
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Unshare Link Exception ${result.logMessage}")
} }
return RemoteOperationResult(method)
}
private fun onRequestSuccessful(response: String?): RemoteOperationResult<Unit> {
val result = RemoteOperationResult<Unit>(RemoteOperationResult.ResultCode.OK)
Timber.d("Successful response: $response")
Timber.d("*** Unshare link completed ")
return result return result
} }
override fun run(client: OwnCloudClient): RemoteOperationResult<Unit> {
val requestUri = buildRequestUri(client.baseUri)
val deleteMethod = DeleteMethod(URL(requestUri.toString())).apply {
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
return try {
val status = client.executeHttpMethod(deleteMethod)
val response = deleteMethod.getResponseBodyAsString()
if (isSuccess(status)) {
onRequestSuccessful(response)
} else {
onResultUnsuccessful(deleteMethod, response, status)
}
} catch (e: Exception) {
Timber.e(e, "Exception while unshare link")
RemoteOperationResult(e)
}
}
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object {
// OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
}
} }

View File

@ -25,4 +25,4 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
class ShareParserResult(val shares: List<RemoteShare>) data class ShareResponse(val shares: List<RemoteShare>)

View File

@ -1,120 +0,0 @@
/* ownCloud Android Library is available under MIT license
* @author David A. Velasco
* @author David González Verdugo
* @author Christian Schabesberger
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import org.xmlpull.v1.XmlPullParserException
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.IOException
import java.util.ArrayList
class ShareToRemoteOperationResultParser(private var shareXmlParser: ShareXMLParser?) {
var oneOrMoreSharesRequired = false
var ownCloudVersion: OwnCloudVersion? = null
var serverBaseUri: Uri? = null
fun parse(serverResponse: String?): RemoteOperationResult<ShareParserResult> {
if (serverResponse.isNullOrEmpty()) {
return RemoteOperationResult(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE)
}
var result: RemoteOperationResult<ShareParserResult>
val resultData: List<RemoteShare>?
try {
// Parse xml response and obtain the list of shares
val byteArrayServerResponse = ByteArrayInputStream(serverResponse.toByteArray())
if (shareXmlParser == null) {
Timber.w("No ShareXmlParser provided, creating new instance")
shareXmlParser = ShareXMLParser()
}
val shares = shareXmlParser?.parseXMLResponse(byteArrayServerResponse)
when {
shareXmlParser?.isSuccess!! -> {
if (!shares.isNullOrEmpty() || !oneOrMoreSharesRequired) {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.OK)
resultData = shares?.map { share ->
if (share.shareType != ShareType.PUBLIC_LINK ||
share.shareLink.isNotEmpty() ||
share.token.isEmpty()
) {
return@map share
}
if (serverBaseUri != null) {
val sharingLinkPath = ShareUtils.SHARING_LINK_PATH
share.shareLink = serverBaseUri.toString() + sharingLinkPath + share.token
} else {
Timber.e("Couldn't build link for public share :(")
}
share
}
if (resultData != null) {
result.setData(ShareParserResult(ArrayList(resultData.toMutableList())))
}
} else {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE)
Timber.e("Successful status with no share in the response")
}
}
shareXmlParser?.isWrongParameter!! -> {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_WRONG_PARAMETER)
result.httpPhrase = shareXmlParser?.message
}
shareXmlParser?.isNotFound!! -> {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND)
result.httpPhrase = shareXmlParser?.message
}
shareXmlParser?.isForbidden!! -> {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_FORBIDDEN)
result.httpPhrase = shareXmlParser?.message
}
else -> {
result = RemoteOperationResult(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE)
}
}
} catch (e: XmlPullParserException) {
Timber.e(e, "Error parsing response from server")
result = RemoteOperationResult(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE)
} catch (e: IOException) {
Timber.e(e, "Error reading response from server")
result = RemoteOperationResult(RemoteOperationResult.ResultCode.WRONG_SERVER_RESPONSE)
}
return result
}
}

View File

@ -1,420 +0,0 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2020 ownCloud GmbH.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.resources.shares
import android.util.Xml
import com.owncloud.android.lib.common.network.WebdavUtils
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.ArrayList
/**
* Parser for Share API Response
*
* @author masensio
* @author David González Verdugo
*/
class ShareXMLParser {
// Getters and Setters
var status: String? = null
var statusCode: Int = 0
var message: String? = null
val isSuccess: Boolean
get() = statusCode == SUCCESS
val isForbidden: Boolean
get() = statusCode == ERROR_FORBIDDEN
val isNotFound: Boolean
get() = statusCode == ERROR_NOT_FOUND
val isWrongParameter: Boolean
get() = statusCode == ERROR_WRONG_PARAMETER
// Constructor
init {
statusCode = INIT
}
/**
* Parse is as response of Share API
* @param inputStream
* @return List of ShareRemoteFiles
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
fun parseXMLResponse(inputStream: InputStream): ArrayList<RemoteShare> {
try {
// XMLPullParser
val factory = XmlPullParserFactory.newInstance()
factory.isNamespaceAware = true
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(inputStream, null)
parser.nextTag()
return readOCS(parser)
} finally {
inputStream.close()
}
}
/**
* Parse OCS node
* @param parser
* @return List of ShareRemoteFiles
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun readOCS(parser: XmlPullParser): ArrayList<RemoteShare> {
var shares = ArrayList<RemoteShare>()
parser.require(XmlPullParser.START_TAG, ns, NODE_OCS)
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val name = parser.name
// read NODE_META and NODE_DATA
when {
name.equals(NODE_META, ignoreCase = true) -> {
readMeta(parser)
}
name.equals(NODE_DATA, ignoreCase = true) -> {
shares = readData(parser)
}
else -> {
skip(parser)
}
}
}
return shares
}
/**
* Parse Meta node
* @param parser
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun readMeta(parser: XmlPullParser) {
parser.require(XmlPullParser.START_TAG, ns, NODE_META)
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val name = parser.name
when {
name.equals(NODE_STATUS, ignoreCase = true) -> {
status = readNode(parser, NODE_STATUS)
}
name.equals(NODE_STATUS_CODE, ignoreCase = true) -> {
statusCode = Integer.parseInt(readNode(parser, NODE_STATUS_CODE))
}
name.equals(NODE_MESSAGE, ignoreCase = true) -> {
message = readNode(parser, NODE_MESSAGE)
}
else -> {
skip(parser)
}
}
}
}
/**
* Parse Data node
* @param parser
* @return
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun readData(parser: XmlPullParser): ArrayList<RemoteShare> {
val shares = ArrayList<RemoteShare>()
var share: RemoteShare? = null
parser.require(XmlPullParser.START_TAG, ns, NODE_DATA)
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val name = parser.name
when {
name.equals(NODE_ELEMENT, ignoreCase = true) -> {
readElement(parser, shares)
}
name.equals(NODE_ID, ignoreCase = true) -> {// Parse Create XML Response
share = RemoteShare()
val value = readNode(parser, NODE_ID)
share.id = value
}
name.equals(NODE_URL, ignoreCase = true) -> {
// NOTE: this field is received in all the public shares from OC 9.0.0
// in previous versions, it's received in the result of POST requests, but not
// in GET requests
share!!.shareType = ShareType.PUBLIC_LINK
val value = readNode(parser, NODE_URL)
share.shareLink = value
}
name.equals(NODE_TOKEN, ignoreCase = true) -> {
share!!.token = readNode(parser, NODE_TOKEN)
}
else -> {
skip(parser)
}
}
}
if (share != null) {
// this is the response of a request for creation; don't pass to isValidShare()
shares.add(share)
}
return shares
}
/**
* Parse Element node
* @param parser
* @return
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun readElement(parser: XmlPullParser, shares: ArrayList<RemoteShare>) {
parser.require(XmlPullParser.START_TAG, ns, NODE_ELEMENT)
val remoteShare = RemoteShare()
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val name = parser.name
when {
name.equals(NODE_ELEMENT, ignoreCase = true) -> {
// patch to work around servers responding with extra <element> surrounding all
// the shares on the same file before
// https://github.com/owncloud/core/issues/6992 was fixed
readElement(parser, shares)
}
name.equals(NODE_ID, ignoreCase = true) -> {
remoteShare.id = readNode(parser, NODE_ID)
}
name.equals(NODE_ITEM_TYPE, ignoreCase = true) -> {
remoteShare.isFolder = readNode(parser, NODE_ITEM_TYPE).equals(TYPE_FOLDER, ignoreCase = true)
fixPathForFolder(remoteShare)
}
name.equals(NODE_PARENT, ignoreCase = true) -> {
readNode(parser, NODE_PARENT)
}
name.equals(NODE_SHARE_TYPE, ignoreCase = true) -> {
val value = Integer.parseInt(readNode(parser, NODE_SHARE_TYPE))
remoteShare.shareType = ShareType.fromValue(value)
}
name.equals(NODE_SHARE_WITH, ignoreCase = true) -> {
remoteShare.shareWith = readNode(parser, NODE_SHARE_WITH)
}
name.equals(NODE_PATH, ignoreCase = true) -> {
remoteShare.path = readNode(parser, NODE_PATH)
fixPathForFolder(remoteShare)
}
name.equals(NODE_PERMISSIONS, ignoreCase = true) -> {
remoteShare.permissions = Integer.parseInt(readNode(parser, NODE_PERMISSIONS))
}
name.equals(NODE_STIME, ignoreCase = true) -> {
remoteShare.sharedDate = java.lang.Long.parseLong(readNode(parser, NODE_STIME))
}
name.equals(NODE_EXPIRATION, ignoreCase = true) -> {
val value = readNode(parser, NODE_EXPIRATION)
if (value.isNotEmpty()) {
remoteShare.expirationDate = WebdavUtils.parseResponseDate(value)!!.time
}
}
name.equals(NODE_TOKEN, ignoreCase = true) -> {
remoteShare.token = readNode(parser, NODE_TOKEN)
}
name.equals(NODE_STORAGE, ignoreCase = true) -> {
readNode(parser, NODE_STORAGE)
}
name.equals(NODE_MAIL_SEND, ignoreCase = true) -> {
readNode(parser, NODE_MAIL_SEND)
}
name.equals(NODE_SHARE_WITH_DISPLAY_NAME, ignoreCase = true) -> {
remoteShare.sharedWithDisplayName = readNode(parser, NODE_SHARE_WITH_DISPLAY_NAME)
}
name.equals(NODE_SHARE_WITH_ADDITIONAL_INFO, ignoreCase = true) -> {
remoteShare.sharedWithAdditionalInfo = readNode(parser, NODE_SHARE_WITH_ADDITIONAL_INFO)
}
name.equals(NODE_URL, ignoreCase = true) -> {
val value = readNode(parser, NODE_URL)
remoteShare.shareLink = value
}
name.equals(NODE_NAME, ignoreCase = true) -> {
remoteShare.name = readNode(parser, NODE_NAME)
}
else -> {
skip(parser)
}
}
}
shares.add(remoteShare)
}
private fun fixPathForFolder(share: RemoteShare) {
if (share.isFolder && share.path.isNotEmpty() &&
!share.path.endsWith(File.separator)
) {
share.path = share.path + File.separator
}
}
/**
* Parse a node, to obtain its text. Needs readText method
* @param parser
* @param node
* @return Text of the node
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun readNode(parser: XmlPullParser, node: String): String {
parser.require(XmlPullParser.START_TAG, ns, node)
val value = readText(parser)
parser.require(XmlPullParser.END_TAG, ns, node)
return value
}
/**
* Read the text from a node
* @param parser
* @return Text of the node
* @throws IOException
* @throws XmlPullParserException
*/
@Throws(IOException::class, XmlPullParserException::class)
private fun readText(parser: XmlPullParser): String {
var result = ""
if (parser.next() == XmlPullParser.TEXT) {
result = parser.text
parser.nextTag()
}
return result
}
/**
* Skip tags in parser procedure
* @param parser
* @throws XmlPullParserException
* @throws IOException
*/
@Throws(XmlPullParserException::class, IOException::class)
private fun skip(parser: XmlPullParser) {
if (parser.eventType != XmlPullParser.START_TAG) {
throw IllegalStateException()
}
var depth = 1
while (depth != 0) {
when (parser.next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
}
}
}
companion object {
// No namespaces
private val ns: String? = null
// NODES for XML Parser
private const val NODE_OCS = "ocs"
private const val NODE_META = "meta"
private const val NODE_STATUS = "status"
private const val NODE_STATUS_CODE = "statuscode"
private const val NODE_MESSAGE = "message"
private const val NODE_DATA = "data"
private const val NODE_ELEMENT = "element"
private const val NODE_ID = "id"
private const val NODE_ITEM_TYPE = "item_type"
private const val NODE_PARENT = "parent"
private const val NODE_SHARE_TYPE = "share_type"
private const val NODE_SHARE_WITH = "share_with"
private const val NODE_PATH = "path"
private const val NODE_PERMISSIONS = "permissions"
private const val NODE_STIME = "stime"
private const val NODE_EXPIRATION = "expiration"
private const val NODE_TOKEN = "token"
private const val NODE_STORAGE = "storage"
private const val NODE_MAIL_SEND = "mail_send"
private const val NODE_SHARE_WITH_DISPLAY_NAME = "share_with_displayname"
private const val NODE_SHARE_WITH_ADDITIONAL_INFO = "share_with_additional_info"
private const val NODE_NAME = "name"
private const val NODE_URL = "url"
private const val TYPE_FOLDER = "folder"
private const val SUCCESS = 200
private const val ERROR_WRONG_PARAMETER = 400
private const val ERROR_FORBIDDEN = 403
private const val ERROR_NOT_FOUND = 404
private const val INIT = -1
}
}

View File

@ -1,6 +1,9 @@
/* ownCloud Android Library is available under MIT license /* ownCloud Android Library is available under MIT license
* * @author masensio
* Copyright (C) 2020 ownCloud GmbH. * @author David A. Velasco
* @author David González Verdugo
* @author Fernando Sanz Velasco
* Copyright (C) 2021 ownCloud GmbH
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -25,14 +28,23 @@
package com.owncloud.android.lib.resources.shares package com.owncloud.android.lib.resources.shares
import android.net.Uri
import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.http.HttpConstants import com.owncloud.android.lib.common.http.HttpConstants
import com.owncloud.android.lib.common.http.HttpConstants.PARAM_FORMAT
import com.owncloud.android.lib.common.http.HttpConstants.VALUE_FORMAT
import com.owncloud.android.lib.common.http.methods.nonwebdav.PutMethod import com.owncloud.android.lib.common.http.methods.nonwebdav.PutMethod
import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.CommonOcsResponse
import com.owncloud.android.lib.resources.shares.RemoteShare.Companion.DEFAULT_PERMISSION import com.owncloud.android.lib.resources.shares.RemoteShare.Companion.DEFAULT_PERMISSION
import com.owncloud.android.lib.resources.shares.responses.ShareItem
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import okhttp3.FormBody import okhttp3.FormBody
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.Type
import java.net.URL import java.net.URL
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -46,6 +58,7 @@ import java.util.Locale
* *
* @author David A. Velasco * @author David A. Velasco
* @author David González Verdugo * @author David González Verdugo
* @author Fernando Sanz Velasco
*/ */
class UpdateRemoteShareOperation class UpdateRemoteShareOperation
/** /**
@ -57,7 +70,7 @@ class UpdateRemoteShareOperation
*/ */
private val remoteId: String private val remoteId: String
) : RemoteOperation<ShareParserResult>() { ) : RemoteOperation<ShareResponse>() {
/** /**
* Name to update in Share resource. Ignored by servers previous to version 10.0.0 * Name to update in Share resource. Ignored by servers previous to version 10.0.0
* *
@ -90,109 +103,128 @@ class UpdateRemoteShareOperation
*/ */
var permissions: Int = DEFAULT_PERMISSION var permissions: Int = DEFAULT_PERMISSION
/**
* Enable upload permissions to update in Share resource.
*
* Null results in no update applied to the upload permission.
*/
var publicUpload: Boolean? = null
var retrieveShareDetails = false // To retrieve more info about the just updated share var retrieveShareDetails = false // To retrieve more info about the just updated share
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareParserResult> { private fun buildRequestUri(baseUri: Uri) =
var result: RemoteOperationResult<ShareParserResult> baseUri.buildUpon()
.appendEncodedPath(OCS_ROUTE)
.appendEncodedPath(remoteId)
.appendQueryParameter(PARAM_FORMAT, VALUE_FORMAT)
.build()
try { private fun parseResponse(response: String): ShareResponse {
val formBodyBuilder = FormBody.Builder() val moshi = Moshi.Builder().build()
val commonOcsType: Type = Types.newParameterizedType(CommonOcsResponse::class.java, ShareItem::class.java)
val adapter: JsonAdapter<CommonOcsResponse<ShareItem>> = moshi.adapter(commonOcsType)
val remoteShare = adapter.fromJson(response)?.ocs?.data?.toRemoteShare()
return ShareResponse(remoteShare?.let { listOf(it) } ?: listOf())
}
// Parameters to update private fun onResultUnsuccessful(
if (name != null) { method: PutMethod,
formBodyBuilder.add(PARAM_NAME, name!!) response: String?,
} status: Int
): RemoteOperationResult<ShareResponse> {
Timber.e("Failed response while while updating remote shares ")
if (response != null) {
Timber.e("*** status code: $status; response message: $response")
} else {
Timber.e("*** status code: $status")
}
return RemoteOperationResult(method)
}
if (password != null) { private fun onRequestSuccessful(response: String?): RemoteOperationResult<ShareResponse> {
formBodyBuilder.add(PARAM_PASSWORD, password!!) val result = RemoteOperationResult<ShareResponse>(RemoteOperationResult.ResultCode.OK)
} Timber.d("Successful response: $response")
result.data = parseResponse(response!!)
Timber.d("*** Retrieve the index of the new share completed ")
val emptyShare = result.data.shares.first()
if (expirationDateInMillis < INITIAL_EXPIRATION_DATE_IN_MILLIS) { return if (retrieveShareDetails) {
// clear expiration date // retrieve more info - PUT only returns the index of the new share
formBodyBuilder.add(PARAM_EXPIRATION_DATE, "") GetRemoteShareOperation(emptyShare.id).execute(client)
} else {
result
}
}
} else if (expirationDateInMillis > INITIAL_EXPIRATION_DATE_IN_MILLIS) { private fun createFormBodyBuilder(): FormBody.Builder {
// set expiration date val formBodyBuilder = FormBody.Builder()
val dateFormat = SimpleDateFormat(FORMAT_EXPIRATION_DATE, Locale.getDefault())
val expirationDate = Calendar.getInstance()
expirationDate.timeInMillis = expirationDateInMillis
val formattedExpirationDate = dateFormat.format(expirationDate.time)
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
} // else, ignore - no update
if (publicUpload != null) { // Parameters to update
formBodyBuilder.add(PARAM_PUBLIC_UPLOAD, publicUpload.toString()) if (name != null) {
} formBodyBuilder.add(PARAM_NAME, name.orEmpty())
// IMPORTANT: permissions parameter needs to be updated after mPublicUpload parameter,
// otherwise they would be set always as 1 (READ) in the server when mPublicUpload was updated
if (permissions > DEFAULT_PERMISSION) {
// set permissions
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
}
val requestUri = client.baseUri
val uriBuilder = requestUri.buildUpon()
uriBuilder.appendEncodedPath(ShareUtils.SHARING_API_PATH)
uriBuilder.appendEncodedPath(remoteId.toString())
val putMethod = PutMethod(URL(uriBuilder.build().toString()), formBodyBuilder.build())
putMethod.setRequestHeader(HttpConstants.CONTENT_TYPE_HEADER, HttpConstants.CONTENT_TYPE_URLENCODED_UTF8)
putMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
val status = client.executeHttpMethod(putMethod)
// Parse xml response
val parser = ShareToRemoteOperationResultParser(
ShareXMLParser()
)
if (!isSuccess(status)) {
return parser.parse(putMethod.getResponseBodyAsString())
}
parser.ownCloudVersion = client.ownCloudVersion
parser.serverBaseUri = client.baseUri
result = parser.parse(putMethod.getResponseBodyAsString())
if (result.isSuccess && retrieveShareDetails) {
// retrieve more info - PUT only returns the index of the new share
val emptyShare = result.data.shares.first()
val getInfo = GetRemoteShareOperation(
emptyShare.id
)
result = getInfo.execute(client)
}
} catch (e: Exception) {
result = RemoteOperationResult(e)
Timber.e(e, "Exception while Creating New Share")
} }
return result if (password != null) {
formBodyBuilder.add(PARAM_PASSWORD, password.orEmpty())
}
if (expirationDateInMillis < INITIAL_EXPIRATION_DATE_IN_MILLIS) {
// clear expiration date
formBodyBuilder.add(PARAM_EXPIRATION_DATE, "")
} else if (expirationDateInMillis > INITIAL_EXPIRATION_DATE_IN_MILLIS) {
// set expiration date
val dateFormat = SimpleDateFormat(FORMAT_EXPIRATION_DATE, Locale.getDefault())
val expirationDate = Calendar.getInstance()
expirationDate.timeInMillis = expirationDateInMillis
val formattedExpirationDate = dateFormat.format(expirationDate.time)
formBodyBuilder.add(PARAM_EXPIRATION_DATE, formattedExpirationDate)
} // else, ignore - no update
// IMPORTANT: permissions parameter needs to be updated after mPublicUpload parameter,
// otherwise they would be set always as 1 (READ) in the server when mPublicUpload was updated
if (permissions > DEFAULT_PERMISSION) {
// set permissions
formBodyBuilder.add(PARAM_PERMISSIONS, permissions.toString())
}
return formBodyBuilder
}
override fun run(client: OwnCloudClient): RemoteOperationResult<ShareResponse> {
val requestUri = buildRequestUri(client.baseUri)
val formBodyBuilder = createFormBodyBuilder()
val putMethod = PutMethod(URL(requestUri.toString()), formBodyBuilder.build()).apply {
setRequestHeader(HttpConstants.CONTENT_TYPE_HEADER, HttpConstants.CONTENT_TYPE_URLENCODED_UTF8)
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}
return try {
val status = client.executeHttpMethod(putMethod)
val response = putMethod.getResponseBodyAsString()
if (isSuccess(status)) {
onRequestSuccessful(response)
} else {
onResultUnsuccessful(putMethod, response, status)
}
} catch (e: Exception) {
Timber.e(e, "Exception while updating remote share")
RemoteOperationResult(e)
}
} }
private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK
companion object { companion object {
//OCS Route
private const val OCS_ROUTE = "ocs/v2.php/apps/files_sharing/api/v1/shares"
//Arguments - names
private const val PARAM_NAME = "name" private const val PARAM_NAME = "name"
private const val PARAM_PASSWORD = "password" private const val PARAM_PASSWORD = "password"
private const val PARAM_EXPIRATION_DATE = "expireDate" private const val PARAM_EXPIRATION_DATE = "expireDate"
private const val PARAM_PERMISSIONS = "permissions" private const val PARAM_PERMISSIONS = "permissions"
private const val PARAM_PUBLIC_UPLOAD = "publicUpload"
private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"
private const val ENTITY_CONTENT_TYPE = "application/x-www-form-urlencoded"
private const val ENTITY_CHARSET = "UTF-8"
//Arguments - constant values
private const val FORMAT_EXPIRATION_DATE = "yyyy-MM-dd"
private const val INITIAL_EXPIRATION_DATE_IN_MILLIS: Long = 0 private const val INITIAL_EXPIRATION_DATE_IN_MILLIS: Long = 0
} }
} }

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