Skip to content

Commit 625a054

Browse files
committed
feat: map Synology errors to FailureReasons and improve exception handling
- Add NO_NAS_IN_ACCOUNT to global FailureReasons enum. - Extend SynologyException from CopyExceptionWithFailureReason to allow returning specific FailureReasons (NO_NAS_IN_ACCOUNT, DESTINATION_FULL). - Update SynologyDTPService to parse specific Synology error codes (2000, 2001) and HTTP 413 to throw exceptions with mapped failure reasons. - Refactor SynologyUploader to use standard for-loops instead of streams to properly support checked exceptions. - Update SynologyMediaAlbumBinder to use synchronized blocks for better concurrency control and exception propagation. - Catch and rethrow SynologyException in importers to ensure failure reasons are propagated to the monitor/job manager.
1 parent 52fbfae commit 625a054

12 files changed

Lines changed: 229 additions & 111 deletions

File tree

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/constant/SynologyErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ public interface SynologyErrorCode {
4242
// DTP Client Errors
4343
public static final int MAX_RETRIES_EXCEEDED = 11001;
4444
public static final int IMPORT_FAILED = 11002;
45+
public static final int QUOTA_NOT_CLAIMED = 11003;
46+
public static final int DESTINATION_FULL = 11004;
4547
}

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/exceptions/SynologyException.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717

1818
package org.datatransferproject.datatransfer.synology.exceptions;
1919

20+
import javax.annotation.Nonnull;
21+
import org.datatransferproject.datatransfer.synology.constant.SynologyErrorCode;
22+
import org.datatransferproject.spi.transfer.types.CopyExceptionWithFailureReason;
23+
import org.datatransferproject.spi.transfer.types.FailureReasons;
24+
2025
/** Exception thrown when an error occurs during a Synology import / export operation. */
21-
public class SynologyException extends RuntimeException {
26+
public class SynologyException extends CopyExceptionWithFailureReason {
2227
private final String errorCode;
2328

2429
/**
2530
* @param message error message
2631
*/
2732
protected SynologyException(String message) {
28-
super(message);
33+
super(message, new RuntimeException(message));
2934
this.errorCode = null;
3035
}
3136

@@ -34,7 +39,7 @@ protected SynologyException(String message) {
3439
* @param errorCode the error code
3540
*/
3641
protected SynologyException(String message, String errorCode) {
37-
super(message);
42+
super(message, new RuntimeException(message));
3843
this.errorCode = errorCode;
3944
}
4045

@@ -51,4 +56,19 @@ protected SynologyException(String message, Throwable cause, String errorCode) {
5156
public String getErrorCode() {
5257
return errorCode;
5358
}
59+
60+
@Nonnull
61+
@Override
62+
public String getFailureReason() {
63+
if (errorCode == null) {
64+
return FailureReasons.UPLOAD_ERROR.toString();
65+
}
66+
if (errorCode.equals(Integer.toString(SynologyErrorCode.QUOTA_NOT_CLAIMED))) {
67+
return FailureReasons.NO_NAS_IN_ACCOUNT.toString();
68+
}
69+
if (errorCode.equals(Integer.toString(SynologyErrorCode.DESTINATION_FULL))) {
70+
return FailureReasons.DESTINATION_FULL.toString();
71+
}
72+
return FailureReasons.UPLOAD_ERROR.toString();
73+
}
5474
}

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/exceptions/SynologyImportException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public SynologyImportException(String message) {
2525
super(message, "IMPORT_ERROR");
2626
}
2727

28+
public SynologyImportException(String message, String errorCode) {
29+
super(message, errorCode);
30+
}
31+
2832
public SynologyImportException(String message, Throwable cause) {
2933
super(message, cause, String.valueOf(SynologyErrorCode.IMPORT_FAILED));
3034
}

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/media/SynologyMediaImporter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.UUID;
44
import org.datatransferproject.api.launcher.Monitor;
5+
import org.datatransferproject.datatransfer.synology.exceptions.SynologyException;
56
import org.datatransferproject.datatransfer.synology.service.SynologyOAuthTokenManager;
67
import org.datatransferproject.datatransfer.synology.uploader.SynologyUploader;
78
import org.datatransferproject.spi.transfer.idempotentexecutor.IdempotentImportExecutor;
@@ -39,6 +40,9 @@ public ImportResult importItem(
3940
synologyUploader.importAlbums(data.getAlbums(), jobId);
4041
synologyUploader.importPhotos(data.getPhotos(), jobId);
4142
synologyUploader.importVideos(data.getVideos(), jobId);
43+
} catch (SynologyException e) {
44+
monitor.severe(() -> "[SynologyImporter] SynologyMediaImporter failed to import data:" + e);
45+
throw e;
4246
} catch (Exception e) {
4347
monitor.severe(() -> "[SynologyImporter] SynologyMediaImporter failed to import data:" + e);
4448
return new ImportResult(e);

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/photos/SynologyPhotosImporter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.UUID;
44
import org.datatransferproject.api.launcher.Monitor;
5+
import org.datatransferproject.datatransfer.synology.exceptions.SynologyException;
56
import org.datatransferproject.datatransfer.synology.service.SynologyOAuthTokenManager;
67
import org.datatransferproject.datatransfer.synology.uploader.SynologyUploader;
78
import org.datatransferproject.spi.transfer.idempotentexecutor.IdempotentImportExecutor;
@@ -38,6 +39,9 @@ public ImportResult importItem(
3839
try {
3940
synologyUploader.importAlbums(data.getAlbums(), jobId);
4041
synologyUploader.importPhotos(data.getPhotos(), jobId);
42+
} catch (SynologyException e) {
43+
monitor.severe(() -> "[SynologyImporter] SynologyPhotosImporter failed to import data:" + e);
44+
throw e;
4145
} catch (Exception e) {
4246
monitor.severe(() -> "[SynologyImporter] SynologyPhotosImporter failed to import data:" + e);
4347
return new ImportResult(e);

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/service/SynologyDTPService.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.io.IOException;
2525
import java.io.InputStream;
2626
import java.net.MalformedURLException;
27-
import java.util.Date;
2827
import java.net.URL;
2928
import java.util.Date;
3029
import java.util.Map;
@@ -37,6 +36,8 @@
3736
import okhttp3.RequestBody;
3837
import okhttp3.Response;
3938
import org.datatransferproject.api.launcher.Monitor;
39+
import org.datatransferproject.datatransfer.synology.constant.SynologyErrorCode;
40+
import org.datatransferproject.datatransfer.synology.exceptions.SynologyException;
4041
import org.datatransferproject.datatransfer.synology.exceptions.SynologyHttpException;
4142
import org.datatransferproject.datatransfer.synology.exceptions.SynologyImportException;
4243
import org.datatransferproject.datatransfer.synology.exceptions.SynologyMaxRetriesExceededException;
@@ -106,7 +107,7 @@ protected InputStream getInputStream(String url) throws IOException {
106107
* @param jobId the job ID
107108
* @return a map of shape {"data": {"album_id": <album_id>}}
108109
*/
109-
public Map<String, Object> createAlbum(MediaAlbum album, UUID jobId) {
110+
public Map<String, Object> createAlbum(MediaAlbum album, UUID jobId) throws SynologyException {
110111
FormBody.Builder builder = new FormBody.Builder().add("title", album.getName());
111112
builder.add("album_id", album.getId());
112113
builder.add("job_id", jobId.toString());
@@ -124,7 +125,7 @@ public Map<String, Object> createAlbum(MediaAlbum album, UUID jobId) {
124125
* @param jobId the job ID
125126
* @return a map of shape {"data": {"item_id": <item_id>}}
126127
*/
127-
public Map<String, Object> createPhoto(PhotoModel photo, UUID jobId) {
128+
public Map<String, Object> createPhoto(PhotoModel photo, UUID jobId) throws SynologyException {
128129
byte[] imageBytes;
129130
try {
130131
InputStream inputStream = null;
@@ -178,7 +179,7 @@ public Map<String, Object> createPhoto(PhotoModel photo, UUID jobId) {
178179
* @param jobId the job ID
179180
* @return a map of shape {"data": {"item_id": <item_id>}}
180181
*/
181-
public Map<String, Object> createVideo(VideoModel video, UUID jobId) {
182+
public Map<String, Object> createVideo(VideoModel video, UUID jobId) throws SynologyException {
182183
byte[] videoBytes;
183184
try {
184185
InputStream inputStream = null;
@@ -232,7 +233,8 @@ public Map<String, Object> createVideo(VideoModel video, UUID jobId) {
232233
* @param itemId the item ID
233234
* @return a map of shape {"success": <bool>}
234235
*/
235-
public Map<String, Object> addItemToAlbum(String albumId, String itemId, UUID jobId) {
236+
public Map<String, Object> addItemToAlbum(String albumId, String itemId, UUID jobId)
237+
throws SynologyException {
236238
FormBody.Builder builder =
237239
new FormBody.Builder()
238240
.add("job_id", jobId.toString())
@@ -243,7 +245,40 @@ public Map<String, Object> addItemToAlbum(String albumId, String itemId, UUID jo
243245
}
244246

245247
@VisibleForTesting
246-
protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID jobId) {
248+
protected void checkUnprocessableContent(Response response) throws SynologyImportException {
249+
String errorCode = "";
250+
String errorMessage = "";
251+
252+
try {
253+
assert response.body() != null;
254+
255+
String bodyString = response.body().string();
256+
Map<String, Object> responseData =
257+
(Map<String, Object>) objectMapper.readValue(bodyString, Map.class);
258+
259+
if (!responseData.containsKey("error")) {
260+
return;
261+
}
262+
263+
Map<String, Object> errorData = (Map<String, Object>) responseData.get("error");
264+
errorCode = errorData.getOrDefault("code", "").toString();
265+
errorMessage = errorData.getOrDefault("msg", "").toString();
266+
} catch (Exception e) {
267+
monitor.severe(
268+
() ->
269+
"[SynologyImporter] Failed to parse unprocessable content response data, exception:",
270+
e);
271+
}
272+
273+
if (errorCode.equals("2000") || errorCode.equals("2001")) {
274+
throw new SynologyImportException(
275+
errorMessage, Integer.toString(SynologyErrorCode.QUOTA_NOT_CLAIMED));
276+
}
277+
}
278+
279+
@VisibleForTesting
280+
protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID jobId)
281+
throws SynologyException {
247282
boolean triedRefreshToken = false;
248283
Request.Builder requestBuilder = new Request.Builder().url(url).post(body);
249284

@@ -255,6 +290,13 @@ protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID
255290
response = client.newCall(requestBuilder.build()).execute();
256291
if (!response.isSuccessful()) {
257292
int code = response.code();
293+
if (code == 413) {
294+
throw new SynologyImportException(
295+
"Uploaded file is too large", Integer.toString(SynologyErrorCode.DESTINATION_FULL));
296+
}
297+
if (code == 422) {
298+
checkUnprocessableContent(response);
299+
}
258300
if (code == 401 && !triedRefreshToken) {
259301
triedRefreshToken = true;
260302
if (tokenManager.refreshToken(jobId, client, objectMapper)) {
@@ -263,6 +305,10 @@ protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID
263305
}
264306
throw new SynologyHttpException("Synology request failed", code, response.message());
265307
}
308+
} catch (SynologyImportException e) {
309+
monitor.severe(
310+
() -> "[SynologyImporter] Failed to send post request,", "url:", url, "exception:", e);
311+
throw e;
266312
} catch (Exception e) {
267313
monitor.severe(
268314
() -> "[SynologyImporter] Failed to send post request,", "url:", url, "exception:", e);

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/service/SynologyOAuthTokenManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public SynologyOAuthTokenManager(AppCredentials appCredentials, Monitor monitor)
4242
this.monitor = monitor;
4343
}
4444

45-
public String getAccessToken(UUID jobId) {
45+
public String getAccessToken(UUID jobId) throws SynologyImportException {
4646
if (!authMap.containsKey(jobId)) {
4747
throw new SynologyImportException("No auth data found for job: " + jobId);
4848
}

0 commit comments

Comments
 (0)